From 224c874673d5ef07b1dd1d0b6987cfcdcca1826f Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 22 Apr 2020 20:45:09 +0200 Subject: [PATCH 001/511] UniFi - Improve logging related to loosing connection to controller (#34547) --- homeassistant/components/unifi/controller.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 33e7fc3836b..96f1194df24 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -174,7 +174,7 @@ class UniFiController: if signal == SIGNAL_CONNECTION_STATE: if data == STATE_DISCONNECTED and self.available: - LOGGER.error("Lost connection to UniFi") + LOGGER.warning("Lost connection to UniFi controller") if (data == STATE_RUNNING and not self.available) or ( data == STATE_DISCONNECTED and self.available @@ -183,7 +183,9 @@ class UniFiController: async_dispatcher_send(self.hass, self.signal_reachable) if not self.available: - self.hass.loop.call_later(RETRY_TIMER, self.reconnect) + self.hass.loop.call_later(RETRY_TIMER, self.reconnect, True) + else: + LOGGER.info("Connected to UniFi controller") elif signal == SIGNAL_DATA and data: @@ -292,9 +294,10 @@ class UniFiController: async_dispatcher_send(hass, controller.signal_options_update) @callback - def reconnect(self) -> None: + def reconnect(self, log=False) -> None: """Prepare to reconnect UniFi session.""" - LOGGER.debug("Reconnecting to UniFi in %i", RETRY_TIMER) + if log: + LOGGER.info("Will try to reconnect to UniFi controller") self.hass.loop.create_task(self.async_reconnect()) async def async_reconnect(self) -> None: From dbd1ca45c4b2f5e5d0f2096f7d6db3d6d662f38e Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Wed, 22 Apr 2020 23:10:06 +0200 Subject: [PATCH 002/511] Update tesla-powerwall to version 0.2.8 (#34545) * Update tesla-powerwall to version 0.2.7 * Update tesla-powerwall to version 0.2.8 --- homeassistant/components/powerwall/__init__.py | 5 +++-- homeassistant/components/powerwall/config_flow.py | 5 +++-- homeassistant/components/powerwall/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index c6c508136b2..a76393e350a 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging import requests -from tesla_powerwall import ApiError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -96,8 +96,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): http_session = requests.Session() power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: + await hass.async_add_executor_job(power_wall.detect_and_pin_version) powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall) - except (PowerwallUnreachableError, ApiError, ConnectionError): + except (PowerwallUnreachableError, APIError, ConnectionError): http_session.close() raise ConfigEntryNotReady diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index 403075989e9..ca0e2143454 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Tesla Powerwall integration.""" import logging -from tesla_powerwall import ApiError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -23,8 +23,9 @@ async def validate_input(hass: core.HomeAssistant, data): power_wall = Powerwall(data[CONF_IP_ADDRESS]) try: + await hass.async_add_executor_job(power_wall.detect_and_pin_version) site_info = await hass.async_add_executor_job(power_wall.get_site_info) - except (PowerwallUnreachableError, ApiError, ConnectionError): + except (PowerwallUnreachableError, APIError, ConnectionError): raise CannotConnect # Return info that you want to store in the config entry. diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 9e4c01e1447..7b2095c4a2a 100644 --- a/homeassistant/components/powerwall/manifest.json +++ b/homeassistant/components/powerwall/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla Powerwall", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/powerwall", - "requirements": ["tesla-powerwall==0.2.5"], + "requirements": ["tesla-powerwall==0.2.8"], "codeowners": ["@bdraco", "@jrester"] } diff --git a/requirements_all.txt b/requirements_all.txt index 16fc13cb56c..e9a44259aec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2026,7 +2026,7 @@ temperusb==1.5.3 # tensorflow==1.13.2 # homeassistant.components.powerwall -tesla-powerwall==0.2.5 +tesla-powerwall==0.2.8 # homeassistant.components.tesla teslajsonpy==0.8.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 32f0bf37920..80d318cf0b6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -765,7 +765,7 @@ sunwatcher==0.2.1 tellduslive==0.10.10 # homeassistant.components.powerwall -tesla-powerwall==0.2.5 +tesla-powerwall==0.2.8 # homeassistant.components.tesla teslajsonpy==0.8.0 From 116012680a9ac33b693b476ccdef86669b3f8cdb Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 22 Apr 2020 16:20:14 -0500 Subject: [PATCH 003/511] Add All wrapper to deprecated Plex schema (#34552) --- homeassistant/components/plex/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 3e52b9f54ba..7f8caf8390b 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -76,8 +76,10 @@ SERVER_CONFIG_SCHEMA = vol.Schema( ) CONFIG_SCHEMA = vol.Schema( - cv.deprecated(PLEX_DOMAIN, invalidation_version="0.111"), - {PLEX_DOMAIN: SERVER_CONFIG_SCHEMA}, + vol.All( + cv.deprecated(PLEX_DOMAIN, invalidation_version="0.111"), + {PLEX_DOMAIN: SERVER_CONFIG_SCHEMA}, + ), extra=vol.ALLOW_EXTRA, ) From 6631bbc6f542ec6094dbd159880ad1146c8b19d8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 22 Apr 2020 23:28:36 +0200 Subject: [PATCH 004/511] Remove reconnect logic from MQTT client. (#34556) --- homeassistant/components/mqtt/__init__.py | 28 +---------------------- tests/components/mqtt/test_init.py | 28 ----------------------- 2 files changed, 1 insertion(+), 55 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5c0f3108960..248769099ae 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -9,7 +9,6 @@ from operator import attrgetter import os import ssl import sys -import time from typing import Any, Callable, List, Optional, Union import attr @@ -929,7 +928,6 @@ class MQTT: "Unable to connect to the MQTT broker: %s", mqtt.connack_string(result_code), ) - self._mqttc.disconnect() return self.connected = True @@ -999,31 +997,7 @@ class MQTT: def _mqtt_on_disconnect(self, _mqttc, _userdata, result_code: int) -> None: """Disconnected callback.""" self.connected = False - - # When disconnected because of calling disconnect() - if result_code == 0: - return - - tries = 0 - - while True: - try: - if self._mqttc.reconnect() == 0: - self.connected = True - _LOGGER.info("Successfully reconnected to the MQTT server") - break - except OSError: - pass - - wait_time = min(2 ** tries, MAX_RECONNECT_WAIT) - _LOGGER.warning( - "Disconnected from MQTT (%s). Trying to reconnect in %s s", - result_code, - wait_time, - ) - # It is ok to sleep here as we are in the MQTT thread. - time.sleep(wait_time) - tries += 1 + _LOGGER.warning("Disconnected from MQTT (%s).", result_code) def _raise_on_error(result_code: int) -> None: diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 9b5f70a95ec..75c6d49d156 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -571,34 +571,6 @@ class TestMQTTCallbacks(unittest.TestCase): assert self.calls[0][0].topic == topic assert self.calls[0][0].payload == payload - def test_mqtt_failed_connection_results_in_disconnect(self): - """Test if connection failure leads to disconnect.""" - for result_code in range(1, 6): - self.hass.data["mqtt"]._mqttc = mock.MagicMock() - self.hass.data["mqtt"]._mqtt_on_connect( - None, {"topics": {}}, 0, result_code - ) - assert self.hass.data["mqtt"]._mqttc.disconnect.called - - def test_mqtt_disconnect_tries_no_reconnect_on_stop(self): - """Test the disconnect tries.""" - self.hass.data["mqtt"]._mqtt_on_disconnect(None, None, 0) - assert not self.hass.data["mqtt"]._mqttc.reconnect.called - - @mock.patch("homeassistant.components.mqtt.time.sleep") - def test_mqtt_disconnect_tries_reconnect(self, mock_sleep): - """Test the re-connect tries.""" - self.hass.data["mqtt"].subscriptions = [ - mqtt.Subscription("test/progress", None, 0), - mqtt.Subscription("test/progress", None, 1), - mqtt.Subscription("test/topic", None, 2), - ] - self.hass.data["mqtt"]._mqttc.reconnect.side_effect = [1, 1, 1, 0] - self.hass.data["mqtt"]._mqtt_on_disconnect(None, None, 1) - assert self.hass.data["mqtt"]._mqttc.reconnect.called - assert len(self.hass.data["mqtt"]._mqttc.reconnect.mock_calls) == 4 - assert [call[1][0] for call in mock_sleep.mock_calls] == [1, 2, 4] - def test_retained_message_on_subscribe_received(self): """Test every subscriber receives retained message on subscribe.""" From a8cd7203dfa73104bdcb67cfa61ef683febbb7ff Mon Sep 17 00:00:00 2001 From: Quentame Date: Wed, 22 Apr 2020 23:28:47 +0200 Subject: [PATCH 005/511] Bump python-synology to 0.7.0 (#34534) --- .../components/synology_dsm/__init__.py | 12 +--- .../components/synology_dsm/config_flow.py | 42 ++++--------- .../components/synology_dsm/const.py | 1 - .../components/synology_dsm/manifest.json | 2 +- .../components/synology_dsm/strings.json | 6 +- .../synology_dsm/translations/en.json | 6 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../synology_dsm/test_config_flow.py | 60 ++++++++++++------- 9 files changed, 62 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index f2fd3d3af0c..3fbed6955d9 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -9,7 +9,6 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( - CONF_API_VERSION, CONF_DISKS, CONF_HOST, CONF_PASSWORD, @@ -22,14 +21,13 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType -from .const import CONF_VOLUMES, DEFAULT_DSM_VERSION, DEFAULT_SSL, DOMAIN +from .const import CONF_VOLUMES, DEFAULT_SSL, DOMAIN CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_API_VERSION, default=DEFAULT_DSM_VERSION): cv.positive_int, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_DISKS): cv.ensure_list, @@ -70,12 +68,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): password = entry.data[CONF_PASSWORD] unit = hass.config.units.temperature_unit use_ssl = entry.data[CONF_SSL] - api_version = entry.data.get(CONF_API_VERSION, DEFAULT_DSM_VERSION) device_token = entry.data.get("device_token") - api = SynoApi( - hass, host, port, username, password, unit, use_ssl, device_token, api_version - ) + api = SynoApi(hass, host, port, username, password, unit, use_ssl, device_token) await api.async_setup() @@ -109,7 +104,6 @@ class SynoApi: temp_unit: str, use_ssl: bool, device_token: str, - api_version: int, ): """Initialize the API wrapper class.""" self._hass = hass @@ -119,7 +113,6 @@ class SynoApi: self._password = password self._use_ssl = use_ssl self._device_token = device_token - self._api_version = api_version self.temp_unit = temp_unit self._dsm: SynologyDSM = None @@ -143,7 +136,6 @@ class SynoApi: self._password, self._use_ssl, device_token=self._device_token, - dsm_version=self._api_version, ) await self._hass.async_add_executor_job(self._fetch_device_configuration) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index c478270f8b0..c1e8cf553d9 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -4,16 +4,17 @@ from urllib.parse import urlparse from synology_dsm import SynologyDSM from synology_dsm.exceptions import ( + SynologyDSMException, SynologyDSMLogin2SAFailedException, SynologyDSMLogin2SARequiredException, SynologyDSMLoginInvalidException, + SynologyDSMRequestException, ) import voluptuous as vol from homeassistant import config_entries, exceptions from homeassistant.components import ssdp from homeassistant.const import ( - CONF_API_VERSION, CONF_DISKS, CONF_HOST, CONF_NAME, @@ -23,13 +24,7 @@ from homeassistant.const import ( CONF_USERNAME, ) -from .const import ( - CONF_VOLUMES, - DEFAULT_DSM_VERSION, - DEFAULT_PORT, - DEFAULT_PORT_SSL, - DEFAULT_SSL, -) +from .const import CONF_VOLUMES, DEFAULT_PORT, DEFAULT_PORT_SSL, DEFAULT_SSL from .const import DOMAIN # pylint: disable=unused-import _LOGGER = logging.getLogger(__name__) @@ -56,12 +51,6 @@ def _ordered_shared_schema(schema_input): vol.Required(CONF_PASSWORD, default=schema_input.get(CONF_PASSWORD, "")): str, vol.Optional(CONF_PORT, default=schema_input.get(CONF_PORT, "")): str, vol.Optional(CONF_SSL, default=schema_input.get(CONF_SSL, DEFAULT_SSL)): bool, - vol.Optional( - CONF_API_VERSION, - default=schema_input.get(CONF_API_VERSION, DEFAULT_DSM_VERSION), - ): vol.All( - vol.Coerce(int), vol.In([5, 6]), # DSM versions supported by the library - ), } @@ -111,7 +100,6 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] use_ssl = user_input.get(CONF_SSL, DEFAULT_SSL) - api_version = user_input.get(CONF_API_VERSION, DEFAULT_DSM_VERSION) otp_code = user_input.get(CONF_OTP_CODE) if not port: @@ -120,9 +108,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): else: port = DEFAULT_PORT - api = SynologyDSM( - host, port, username, password, use_ssl, dsm_version=api_version, - ) + api = SynologyDSM(host, port, username, password, use_ssl) try: serial = await self.hass.async_add_executor_job( @@ -134,8 +120,12 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors[CONF_OTP_CODE] = "otp_failed" user_input[CONF_OTP_CODE] = None return await self.async_step_2sa(user_input, errors) - except (SynologyDSMLoginInvalidException, InvalidAuth): + except SynologyDSMLoginInvalidException: errors[CONF_USERNAME] = "login" + except SynologyDSMRequestException: + errors[CONF_HOST] = "connection" + except SynologyDSMException: + errors["base"] = "unknown" except InvalidData: errors["base"] = "missing_data" @@ -152,7 +142,6 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_SSL: use_ssl, CONF_USERNAME: username, CONF_PASSWORD: password, - CONF_API_VERSION: api_version, } if otp_code: config_data["device_token"] = api.device_token @@ -216,28 +205,21 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): def _login_and_fetch_syno_info(api, otp_code): """Login to the NAS and fetch basic data.""" - if not api.login(otp_code): - raise InvalidAuth - # These do i/o - information = api.information + api.login(otp_code) utilisation = api.utilisation storage = api.storage if ( - information.serial is None + api.information.serial is None or utilisation.cpu_user_load is None or storage.disks_ids is None or storage.volumes_ids is None ): raise InvalidData - return information.serial + return api.information.serial class InvalidData(exceptions.HomeAssistantError): """Error to indicate we get invalid data from the nas.""" - - -class InvalidAuth(exceptions.HomeAssistantError): - """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index c0b1fe782c4..8c17de5e997 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -12,7 +12,6 @@ CONF_VOLUMES = "volumes" DEFAULT_SSL = True DEFAULT_PORT = 5000 DEFAULT_PORT_SSL = 5001 -DEFAULT_DSM_VERSION = 6 UTILISATION_SENSORS = { "cpu_other_load": ["CPU Load (Other)", UNIT_PERCENTAGE, "mdi:chip"], diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index b54132c6897..c7abd809b87 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.6.0"], + "requirements": ["python-synology==0.7.0"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index b90fd52fe5f..d00525f995d 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -8,7 +8,6 @@ "host": "Host", "port": "Port (Optional)", "ssl": "Use SSL/TLS to connect to your NAS", - "api_version": "DSM version", "username": "Username", "password": "Password" } @@ -24,7 +23,6 @@ "description": "Do you want to setup {name} ({host})?", "data": { "ssl": "Use SSL/TLS to connect to your NAS", - "api_version": "DSM version", "username": "Username", "password": "Password", "port": "Port (Optional)" @@ -32,9 +30,11 @@ } }, "error": { + "connection": "Connection error: please check your host, password & ssl", "login": "Login error: please check your username & password", "missing_data": "Missing data: please retry later or an other configuration", - "otp_failed": "Two-step authentication failed, retry with a new pass code" + "otp_failed": "Two-step authentication failed, retry with a new pass code", + "unknown": "Unknown error: please check logs to get more details" }, "abort": { "already_configured": "Host already configured" } } diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index 57ee3455840..41814a1e68d 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -4,9 +4,11 @@ "already_configured": "Host already configured" }, "error": { + "connection": "Connection error: please check your host, password & ssl", "login": "Login error: please check your username & password", "missing_data": "Missing data: please retry later or an other configuration", - "otp_failed": "Two-step authentication failed, retry with a new pass code" + "otp_failed": "Two-step authentication failed, retry with a new pass code", + "unknown": "Unknown error: please check logs to get more details" }, "flow_title": "Synology DSM {name} ({host})", "step": { @@ -18,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM version", "password": "Password", "port": "Port (Optional)", "ssl": "Use SSL/TLS to connect to your NAS", @@ -29,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM version", "host": "Host", "password": "Password", "port": "Port (Optional)", diff --git a/requirements_all.txt b/requirements_all.txt index e9a44259aec..8c89391d060 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1680,7 +1680,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.6.0 +python-synology==0.7.0 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 80d318cf0b6..77c12ead2c7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -650,7 +650,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.6.0 +python-synology==0.7.0 # homeassistant.components.tado python-tado==0.8.1 diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 17b98b7c910..9a9283256c5 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -4,8 +4,11 @@ from unittest.mock import MagicMock, Mock, patch import pytest from synology_dsm.exceptions import ( + SynologyDSMException, SynologyDSMLogin2SAFailedException, SynologyDSMLogin2SARequiredException, + SynologyDSMLoginInvalidException, + SynologyDSMRequestException, ) from homeassistant import data_entry_flow, setup @@ -13,7 +16,6 @@ from homeassistant.components import ssdp from homeassistant.components.synology_dsm.config_flow import CONF_OTP_CODE from homeassistant.components.synology_dsm.const import ( CONF_VOLUMES, - DEFAULT_DSM_VERSION, DEFAULT_PORT, DEFAULT_PORT_SSL, DEFAULT_SSL, @@ -21,7 +23,6 @@ from homeassistant.components.synology_dsm.const import ( ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP, SOURCE_USER from homeassistant.const import ( - CONF_API_VERSION, CONF_DISKS, CONF_HOST, CONF_PASSWORD, @@ -76,16 +77,6 @@ def mock_controller_service_2sa(): yield service_mock -@pytest.fixture(name="service_login_failed") -def mock_controller_service_login_failed(): - """Mock a failed login.""" - with patch( - "homeassistant.components.synology_dsm.config_flow.SynologyDSM" - ) as service_mock: - service_mock.return_value.login = Mock(return_value=False) - yield service_mock - - @pytest.fixture(name="service_failed") def mock_controller_service_failed(): """Mock a failed service.""" @@ -117,7 +108,6 @@ async def test_user(hass: HomeAssistantType, service: MagicMock): CONF_SSL: SSL, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, - CONF_API_VERSION: 5, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -128,7 +118,6 @@ async def test_user(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_API_VERSION] == 5 assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -153,7 +142,6 @@ async def test_user(hass: HomeAssistantType, service: MagicMock): assert not result["data"][CONF_SSL] assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_API_VERSION] == DEFAULT_DSM_VERSION assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -216,7 +204,6 @@ async def test_import(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == DEFAULT_SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_API_VERSION] == DEFAULT_DSM_VERSION assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -232,7 +219,6 @@ async def test_import(hass: HomeAssistantType, service: MagicMock): CONF_SSL: SSL, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD, - CONF_API_VERSION: 5, CONF_DISKS: ["sda", "sdb", "sdc"], CONF_VOLUMES: ["volume_1"], }, @@ -245,7 +231,6 @@ async def test_import(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_API_VERSION] == 5 assert result["data"].get("device_token") is None assert result["data"][CONF_DISKS] == ["sda", "sdb", "sdc"] assert result["data"][CONF_VOLUMES] == ["volume_1"] @@ -278,8 +263,12 @@ async def test_abort_if_already_setup(hass: HomeAssistantType, service: MagicMoc assert result["reason"] == "already_configured" -async def test_login_failed(hass: HomeAssistantType, service_login_failed: MagicMock): - """Test when we have errors during connection.""" +async def test_login_failed(hass: HomeAssistantType, service: MagicMock): + """Test when we have errors during login.""" + service.return_value.login = Mock( + side_effect=(SynologyDSMLoginInvalidException(USERNAME)) + ) + result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, @@ -289,6 +278,36 @@ async def test_login_failed(hass: HomeAssistantType, service_login_failed: Magic assert result["errors"] == {CONF_USERNAME: "login"} +async def test_connection_failed(hass: HomeAssistantType, service: MagicMock): + """Test when we have errors during connection.""" + service.return_value.login = Mock( + side_effect=SynologyDSMRequestException(IOError("arg")) + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_HOST: "connection"} + + +async def test_unknown_failed(hass: HomeAssistantType, service: MagicMock): + """Test when we have an unknown error.""" + service.return_value.login = Mock(side_effect=SynologyDSMException) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {"base": "unknown"} + + async def test_missing_data_after_login( hass: HomeAssistantType, service_failed: MagicMock ): @@ -329,7 +348,6 @@ async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == DEFAULT_SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD - assert result["data"][CONF_API_VERSION] == DEFAULT_DSM_VERSION assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None From d6ab36bf8ec305e018c2da7d11aa75c19fb7a343 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 22 Apr 2020 23:29:49 +0200 Subject: [PATCH 006/511] Set mqtt binary_sensor unavailable if expire_after specified (#34259) * Set self._expired=True if expire_after specified * Added test_expiration_on_discovery_and_discovery_update_of_binary_sensor to mqtt/test_binary_sensor.py * Fixed flake8 error * Fixed isort error --- .../hap.cpython-37.pyc.139745820048096 | 0 .../components/mqtt/binary_sensor.py | 6 +- tests/components/mqtt/test_binary_sensor.py | 98 ++++++++++++++++++- 3 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 diff --git a/homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 b/homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index c7595de0eeb..f07be49b421 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -119,7 +119,11 @@ class MqttBinarySensor( self._sub_state = None self._expiration_trigger = None self._delay_listener = None - self._expired = None + expire_after = config.get(CONF_EXPIRE_AFTER) + if expire_after is not None and expire_after > 0: + self._expired = True + else: + self._expired = None device_config = config.get(CONF_DEVICE) MqttAttributes.__init__(self, config) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 07a1c8b6e5f..11fb073f57a 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -4,7 +4,8 @@ from datetime import datetime, timedelta import json from unittest.mock import patch -from homeassistant.components import binary_sensor +from homeassistant.components import binary_sensor, mqtt +from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import ( EVENT_STATE_CHANGED, STATE_OFF, @@ -37,7 +38,11 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) -from tests.common import async_fire_mqtt_message, async_fire_time_changed +from tests.common import ( + MockConfigEntry, + async_fire_mqtt_message, + async_fire_time_changed, +) DEFAULT_CONFIG = { binary_sensor.DOMAIN: { @@ -70,8 +75,9 @@ async def test_setting_sensor_value_expires_availability_topic(hass, mqtt_mock, async_fire_mqtt_message(hass, "availability-topic", "online") + # State should be unavailable since expire_after is defined and > 0 state = hass.states.get("binary_sensor.test") - assert state.state != STATE_UNAVAILABLE + assert state.state == STATE_UNAVAILABLE await expires_helper(hass, mqtt_mock, caplog) @@ -92,15 +98,15 @@ async def test_setting_sensor_value_expires(hass, mqtt_mock, caplog): }, ) + # State should be unavailable since expire_after is defined and > 0 state = hass.states.get("binary_sensor.test") - assert state.state == STATE_OFF + assert state.state == STATE_UNAVAILABLE await expires_helper(hass, mqtt_mock, caplog) async def expires_helper(hass, mqtt_mock, caplog): """Run the basic expiry code.""" - now = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): async_fire_time_changed(hass, now) @@ -460,6 +466,88 @@ async def test_discovery_update_binary_sensor(hass, mqtt_mock, caplog): ) +async def test_expiration_on_discovery_and_discovery_update_of_binary_sensor( + hass, mqtt_mock, caplog +): + """Test that binary_sensor with expire_after set behaves correctly on discovery and discovery update.""" + entry = MockConfigEntry(domain=mqtt.DOMAIN) + await async_start(hass, "homeassistant", {}, entry) + + config = { + "name": "Test", + "state_topic": "test-topic", + "expire_after": 4, + "force_update": True, + } + + config_msg = json.dumps(config) + + # Set time and publish config message to create binary_sensor via discovery with 4 s expiry + now = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_time_changed(hass, now) + async_fire_mqtt_message( + hass, "homeassistant/binary_sensor/bla/config", config_msg + ) + await hass.async_block_till_done() + + # Test that binary_sensor is not available + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_UNAVAILABLE + + # Publish state message + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_mqtt_message(hass, "test-topic", "ON") + await hass.async_block_till_done() + + # Test that binary_sensor has correct state + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_ON + + # Advance +3 seconds + now = now + timedelta(seconds=3) + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + # binary_sensor is not yet expired + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_ON + + # Resend config message to update discovery + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_time_changed(hass, now) + async_fire_mqtt_message( + hass, "homeassistant/binary_sensor/bla/config", config_msg + ) + await hass.async_block_till_done() + + # Test that binary_sensor has not expired + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_ON + + # Add +2 seconds + now = now + timedelta(seconds=2) + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_time_changed(hass, now) + await hass.async_block_till_done() + + # Test that binary_sensor has expired + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_UNAVAILABLE + + # Resend config message to update discovery + with patch(("homeassistant.helpers.event.dt_util.utcnow"), return_value=now): + async_fire_mqtt_message( + hass, "homeassistant/binary_sensor/bla/config", config_msg + ) + await hass.async_block_till_done() + + # Test that binary_sensor is still expired + state = hass.states.get("binary_sensor.test") + assert state.state == STATE_UNAVAILABLE + + async def test_discovery_broken(hass, mqtt_mock, caplog): """Test handling of bad discovery message.""" data1 = '{ "name": "Beer",' ' "off_delay": -1 }' From 4448eb94a1df41eb475aeb4a4aa4e7680482d2d5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 22 Apr 2020 23:38:04 +0200 Subject: [PATCH 007/511] Only subscribe when MQTT client is connected. (#34557) --- homeassistant/components/mqtt/__init__.py | 4 +++- tests/components/mqtt/test_init.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 248769099ae..25b2b0381ea 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -872,7 +872,9 @@ class MQTT: subscription = Subscription(topic, msg_callback, qos, encoding) self.subscriptions.append(subscription) - await self._async_perform_subscription(topic, qos) + # Only subscribe if currently connected. + if self.connected: + await self._async_perform_subscription(topic, qos) @callback def async_remove() -> None: diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 75c6d49d156..290b70953af 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -580,6 +580,9 @@ class TestMQTTCallbacks(unittest.TestCase): self.hass.data["mqtt"]._mqttc.subscribe.side_effect = side_effect + # Fake that the client is connected + self.hass.data["mqtt"].connected = True + calls_a = mock.MagicMock() mqtt.subscribe(self.hass, "test/state", calls_a) self.hass.block_till_done() @@ -592,6 +595,9 @@ class TestMQTTCallbacks(unittest.TestCase): def test_not_calling_unsubscribe_with_active_subscribers(self): """Test not calling unsubscribe() when other subscribers are active.""" + # Fake that the client is connected + self.hass.data["mqtt"].connected = True + unsub = mqtt.subscribe(self.hass, "test/state", None) mqtt.subscribe(self.hass, "test/state", None) self.hass.block_till_done() @@ -603,6 +609,9 @@ class TestMQTTCallbacks(unittest.TestCase): def test_restore_subscriptions_on_reconnect(self): """Test subscriptions are restored on reconnect.""" + # Fake that the client is connected + self.hass.data["mqtt"].connected = True + mqtt.subscribe(self.hass, "test/state", None) self.hass.block_till_done() assert self.hass.data["mqtt"]._mqttc.subscribe.call_count == 1 @@ -614,6 +623,9 @@ class TestMQTTCallbacks(unittest.TestCase): def test_restore_all_active_subscriptions_on_reconnect(self): """Test active subscriptions are restored correctly on reconnect.""" + # Fake that the client is connected + self.hass.data["mqtt"].connected = True + self.hass.data["mqtt"]._mqttc.subscribe.side_effect = ( (0, 1), (0, 2), From 4d292c2723611289e28a3d72ce7eac3ef351799f Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 22 Apr 2020 17:41:14 -0600 Subject: [PATCH 008/511] Add support for AirVisual Node/Pro units (#32815) * Add support for AirVisual Node Pro units * Fixed tests * Updated dependencies * Guard looks cleaner * Limit options update to geography-based entries * Docstring * Use proper precision in display_temp * Add availability for AirVisualNodeProSensor * Updated translations * Samba stuff in play * Wrap up Samba * Fix tests * Remove unnecessary updates * Normalize labels * Bump requirements * Don't include configuration.yaml support for this new functionality * Fix tests * Code review * Code review * Update coveragerc * Code review --- .coveragerc | 1 + .../components/airvisual/__init__.py | 192 +++++++++++++---- .../components/airvisual/air_quality.py | 117 +++++++++++ .../components/airvisual/config_flow.py | 102 +++++++-- homeassistant/components/airvisual/const.py | 10 +- .../components/airvisual/manifest.json | 2 +- homeassistant/components/airvisual/sensor.py | 196 ++++++++++++------ .../components/airvisual/strings.json | 36 +++- .../components/airvisual/translations/en.json | 28 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../components/airvisual/test_config_flow.py | 171 +++++++++++---- 12 files changed, 672 insertions(+), 187 deletions(-) create mode 100644 homeassistant/components/airvisual/air_quality.py diff --git a/.coveragerc b/.coveragerc index a63e1ae33a5..9182edfd756 100644 --- a/.coveragerc +++ b/.coveragerc @@ -21,6 +21,7 @@ omit = homeassistant/components/airly/sensor.py homeassistant/components/airly/const.py homeassistant/components/airvisual/__init__.py + homeassistant/components/airvisual/air_quality.py homeassistant/components/airvisual/sensor.py homeassistant/components/aladdin_connect/cover.py homeassistant/components/alarmdecoder/* diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 4352b15b8a5..7f81b906237 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -1,22 +1,29 @@ """The airvisual component.""" -import logging +import asyncio +from datetime import timedelta from pyairvisual import Client -from pyairvisual.errors import AirVisualError, InvalidKeyError +from pyairvisual.errors import AirVisualError, NodeProError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_API_KEY, + CONF_IP_ADDRESS, CONF_LATITUDE, CONF_LONGITUDE, + CONF_PASSWORD, CONF_SHOW_ON_MAP, CONF_STATE, ) from homeassistant.core import callback -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import aiohttp_client, config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_time_interval from .const import ( @@ -24,15 +31,20 @@ from .const import ( CONF_COUNTRY, CONF_GEOGRAPHIES, DATA_CLIENT, - DEFAULT_SCAN_INTERVAL, DOMAIN, + INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_NODE_PRO, + LOGGER, TOPIC_UPDATE, ) -_LOGGER = logging.getLogger(__name__) +PLATFORMS = ["air_quality", "sensor"] DATA_LISTENER = "listener" +DEFAULT_ATTRIBUTION = "Data provided by AirVisual" +DEFAULT_GEOGRAPHY_SCAN_INTERVAL = timedelta(minutes=10) +DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1) DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True} GEOGRAPHY_COORDINATES_SCHEMA = vol.Schema( @@ -66,6 +78,9 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: CLOUD_API_SCHEMA}, extra=vol.ALLOW_EXTRA) @callback def async_get_geography_id(geography_dict): """Generate a unique ID from a geography dict.""" + if not geography_dict: + return + if CONF_CITY in geography_dict: return ", ".join( ( @@ -103,45 +118,58 @@ async def async_setup(hass, config): return True -async def async_setup_entry(hass, config_entry): - """Set up AirVisual as config entry.""" +@callback +def _standardize_geography_config_entry(hass, config_entry): + """Ensure that geography observables have appropriate properties.""" entry_updates = {} + if not config_entry.unique_id: # If the config entry doesn't already have a unique ID, set one: entry_updates["unique_id"] = config_entry.data[CONF_API_KEY] if not config_entry.options: # If the config entry doesn't already have any options set, set defaults: - entry_updates["options"] = DEFAULT_OPTIONS + entry_updates["options"] = {CONF_SHOW_ON_MAP: True} - if entry_updates: - hass.config_entries.async_update_entry(config_entry, **entry_updates) + if not entry_updates: + return + hass.config_entries.async_update_entry(config_entry, **entry_updates) + + +async def async_setup_entry(hass, config_entry): + """Set up AirVisual as config entry.""" websession = aiohttp_client.async_get_clientsession(hass) - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = AirVisualData( - hass, Client(websession, api_key=config_entry.data[CONF_API_KEY]), config_entry - ) + if CONF_API_KEY in config_entry.data: + _standardize_geography_config_entry(hass, config_entry) + airvisual = AirVisualGeographyData( + hass, + Client(websession, api_key=config_entry.data[CONF_API_KEY]), + config_entry, + ) - try: - await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update() - except InvalidKeyError: - _LOGGER.error("Invalid API key provided") - raise ConfigEntryNotReady + # Only geography-based entries have options: + config_entry.add_update_listener(async_update_options) + else: + airvisual = AirVisualNodeProData(hass, Client(websession), config_entry) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "sensor") - ) + await airvisual.async_update() + + hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airvisual + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) async def refresh(event_time): """Refresh data from AirVisual.""" - await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update() + await airvisual.async_update() hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval( - hass, refresh, DEFAULT_SCAN_INTERVAL + hass, refresh, airvisual.scan_interval ) - config_entry.add_update_listener(async_update_options) - return True @@ -149,7 +177,7 @@ async def async_migrate_entry(hass, config_entry): """Migrate an old config entry.""" version = config_entry.version - _LOGGER.debug("Migrating from version %s", version) + LOGGER.debug("Migrating from version %s", version) # 1 -> 2: One geography per config entry if version == 1: @@ -178,21 +206,27 @@ async def async_migrate_entry(hass, config_entry): ) ) - _LOGGER.info("Migration to version %s successful", version) + LOGGER.info("Migration to version %s successful", version) return True async def async_unload_entry(hass, config_entry): """Unload an AirVisual config entry.""" - hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(config_entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) + remove_listener() - remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) - remove_listener() - - await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") - - return True + return unload_ok async def async_update_options(hass, config_entry): @@ -201,7 +235,53 @@ async def async_update_options(hass, config_entry): airvisual.async_update_options(config_entry.options) -class AirVisualData: +class AirVisualEntity(Entity): + """Define a generic AirVisual entity.""" + + def __init__(self, airvisual): + """Initialize.""" + self._airvisual = airvisual + self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} + self._icon = None + self._unit = None + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + return self._attrs + + @property + def icon(self): + """Return the icon.""" + return self._icon + + @property + def unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return self._unit + + async def async_added_to_hass(self): + """Register callbacks.""" + + @callback + def update(): + """Update the state.""" + self.update_from_latest_data() + self.async_write_ha_state() + + self.async_on_remove( + async_dispatcher_connect(self.hass, self._airvisual.topic_update, update) + ) + + self.update_from_latest_data() + + @callback + def update_from_latest_data(self): + """Update the entity from the latest data.""" + raise NotImplementedError + + +class AirVisualGeographyData: """Define a class to manage data from the AirVisual cloud API.""" def __init__(self, hass, client, config_entry): @@ -211,7 +291,10 @@ class AirVisualData: self.data = {} self.geography_data = config_entry.data self.geography_id = config_entry.unique_id + self.integration_type = INTEGRATION_TYPE_GEOGRAPHY self.options = config_entry.options + self.scan_interval = DEFAULT_GEOGRAPHY_SCAN_INTERVAL + self.topic_update = TOPIC_UPDATE.format(config_entry.unique_id) async def async_update(self): """Get new data for all locations from the AirVisual cloud API.""" @@ -229,14 +312,43 @@ class AirVisualData: try: self.data[self.geography_id] = await api_coro except AirVisualError as err: - _LOGGER.error("Error while retrieving data: %s", err) + LOGGER.error("Error while retrieving data: %s", err) self.data[self.geography_id] = {} - _LOGGER.debug("Received new data") - async_dispatcher_send(self._hass, TOPIC_UPDATE) + LOGGER.debug("Received new geography data") + async_dispatcher_send(self._hass, self.topic_update) @callback def async_update_options(self, options): """Update the data manager's options.""" self.options = options - async_dispatcher_send(self._hass, TOPIC_UPDATE) + async_dispatcher_send(self._hass, self.topic_update) + + +class AirVisualNodeProData: + """Define a class to manage data from an AirVisual Node/Pro.""" + + def __init__(self, hass, client, config_entry): + """Initialize.""" + self._client = client + self._hass = hass + self._password = config_entry.data[CONF_PASSWORD] + self.data = {} + self.integration_type = INTEGRATION_TYPE_NODE_PRO + self.ip_address = config_entry.data[CONF_IP_ADDRESS] + self.scan_interval = DEFAULT_NODE_PRO_SCAN_INTERVAL + self.topic_update = TOPIC_UPDATE.format(config_entry.data[CONF_IP_ADDRESS]) + + async def async_update(self): + """Get new data from the Node/Pro.""" + try: + self.data = await self._client.node.from_samba( + self.ip_address, self._password, include_history=False + ) + except NodeProError as err: + LOGGER.error("Error while retrieving Node/Pro data: %s", err) + self.data = {} + return + + LOGGER.debug("Received new Node/Pro data") + async_dispatcher_send(self._hass, self.topic_update) diff --git a/homeassistant/components/airvisual/air_quality.py b/homeassistant/components/airvisual/air_quality.py new file mode 100644 index 00000000000..9da5b83d79f --- /dev/null +++ b/homeassistant/components/airvisual/air_quality.py @@ -0,0 +1,117 @@ +"""Support for AirVisual Node/Pro units.""" +from homeassistant.components.air_quality import AirQualityEntity +from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER +from homeassistant.core import callback +from homeassistant.util import slugify + +from . import AirVisualEntity +from .const import DATA_CLIENT, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY + +ATTR_HUMIDITY = "humidity" +ATTR_SENSOR_LIFE = "{0}_sensor_life" +ATTR_TREND = "{0}_trend" +ATTR_VOC = "voc" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up AirVisual air quality entities based on a config entry.""" + airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + + # Geography-based AirVisual integrations don't utilize this platform: + if airvisual.integration_type == INTEGRATION_TYPE_GEOGRAPHY: + return + + async_add_entities([AirVisualNodeProSensor(airvisual)], True) + + +class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity): + """Define a sensor for a AirVisual Node/Pro.""" + + def __init__(self, airvisual): + """Initialize.""" + super().__init__(airvisual) + + self._icon = "mdi:chemical-weapon" + self._unit = CONCENTRATION_MICROGRAMS_PER_CUBIC_METER + + @property + def air_quality_index(self): + """Return the Air Quality Index (AQI).""" + if self._airvisual.data["current"]["settings"]["is_aqi_usa"]: + return self._airvisual.data["current"]["measurements"]["aqi_us"] + return self._airvisual.data["current"]["measurements"]["aqi_cn"] + + @property + def available(self): + """Return True if entity is available.""" + return bool(self._airvisual.data) + + @property + def carbon_dioxide(self): + """Return the CO2 (carbon dioxide) level.""" + return self._airvisual.data["current"]["measurements"].get("co2_ppm") + + @property + def device_info(self): + """Return device registry information for this entity.""" + return { + "identifiers": {(DOMAIN, self._airvisual.data["current"]["serial_number"])}, + "name": self._airvisual.data["current"]["settings"]["node_name"], + "manufacturer": "AirVisual", + "model": f'{self._airvisual.data["current"]["status"]["model"]}', + "sw_version": ( + f'Version {self._airvisual.data["current"]["status"]["system_version"]}' + f'{self._airvisual.data["current"]["status"]["app_version"]}' + ), + } + + @property + def name(self): + """Return the name.""" + node_name = self._airvisual.data["current"]["settings"]["node_name"] + return f"{node_name} Node/Pro: Air Quality" + + @property + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self._airvisual.data["current"]["measurements"].get("pm2_5") + + @property + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self._airvisual.data["current"]["measurements"].get("pm1_0") + + @property + def particulate_matter_0_1(self): + """Return the particulate matter 0.1 level.""" + return self._airvisual.data["current"]["measurements"].get("pm0_1") + + @property + def unique_id(self): + """Return a unique, Home Assistant friendly identifier for this entity.""" + return self._airvisual.data["current"]["serial_number"] + + @callback + def update_from_latest_data(self): + """Update from the Node/Pro's data.""" + trends = { + ATTR_TREND.format(slugify(pollutant)): trend + for pollutant, trend in self._airvisual.data["trends"].items() + } + if self._airvisual.data["current"]["settings"]["is_aqi_usa"]: + trends.pop(ATTR_TREND.format("aqi_cn")) + else: + trends.pop(ATTR_TREND.format("aqi_us")) + + self._attrs.update( + { + ATTR_VOC: self._airvisual.data["current"]["measurements"].get("voc"), + **{ + ATTR_SENSOR_LIFE.format(pollutant): lifespan + for pollutant, lifespan in self._airvisual.data["current"][ + "status" + ]["sensor_life"].items() + }, + **trends, + } + ) diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 0c9c0e65ff1..ef15f8dcc99 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -2,21 +2,29 @@ import asyncio from pyairvisual import Client -from pyairvisual.errors import InvalidKeyError +from pyairvisual.errors import InvalidKeyError, NodeProError import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( CONF_API_KEY, + CONF_IP_ADDRESS, CONF_LATITUDE, CONF_LONGITUDE, + CONF_PASSWORD, CONF_SHOW_ON_MAP, ) from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv from . import async_get_geography_id -from .const import CONF_GEOGRAPHIES, DOMAIN # pylint: disable=unused-import +from .const import ( # pylint: disable=unused-import + CONF_GEOGRAPHIES, + DOMAIN, + INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_NODE_PRO, + LOGGER, +) class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -26,7 +34,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL @property - def cloud_api_schema(self): + def geography_schema(self): """Return the data schema for the cloud API.""" return vol.Schema( { @@ -40,38 +48,47 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } ) + @property + def pick_integration_type_schema(self): + """Return the data schema for picking the integration type.""" + return vol.Schema( + { + vol.Required("type"): vol.In( + [INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO] + ) + } + ) + + @property + def node_pro_schema(self): + """Return the data schema for a Node/Pro.""" + return vol.Schema( + {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PASSWORD): str} + ) + async def _async_set_unique_id(self, unique_id): """Set the unique ID of the config flow and abort if it already exists.""" await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() - @callback - async def _show_form(self, errors=None): - """Show the form to the user.""" - return self.async_show_form( - step_id="user", data_schema=self.cloud_api_schema, errors=errors or {}, - ) - @staticmethod @callback def async_get_options_flow(config_entry): """Define the config flow to handle options.""" return AirVisualOptionsFlowHandler(config_entry) - async def async_step_import(self, import_config): - """Import a config entry from configuration.yaml.""" - return await self.async_step_user(import_config) - - async def async_step_user(self, user_input=None): - """Handle the start of the config flow.""" + async def async_step_geography(self, user_input=None): + """Handle the initialization of the integration via the cloud API.""" if not user_input: - return await self._show_form() + return self.async_show_form( + step_id="geography", data_schema=self.geography_schema + ) geo_id = async_get_geography_id(user_input) await self._async_set_unique_id(geo_id) self._abort_if_unique_id_configured() - # Find older config entries without unique ID + # Find older config entries without unique ID: for entry in self._async_current_entries(): if entry.version != 1: continue @@ -97,8 +114,10 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): try: await client.api.nearest_city() except InvalidKeyError: - return await self._show_form( - errors={CONF_API_KEY: "invalid_api_key"} + return self.async_show_form( + step_id="geography", + data_schema=self.geography_schema, + errors={CONF_API_KEY: "invalid_api_key"}, ) checked_keys.add(user_input[CONF_API_KEY]) @@ -107,6 +126,49 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): title=f"Cloud API ({geo_id})", data=user_input ) + async def async_step_import(self, import_config): + """Import a config entry from configuration.yaml.""" + return await self.async_step_geography(import_config) + + async def async_step_node_pro(self, user_input=None): + """Handle the initialization of the integration with a Node/Pro.""" + if not user_input: + return self.async_show_form( + step_id="node_pro", data_schema=self.node_pro_schema + ) + + await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) + + websession = aiohttp_client.async_get_clientsession(self.hass) + client = Client(websession) + + try: + await client.node.from_samba( + user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD] + ) + except NodeProError as err: + LOGGER.error("Error connecting to Node/Pro unit: %s", err) + return self.async_show_form( + step_id="node_pro", + data_schema=self.node_pro_schema, + errors={CONF_IP_ADDRESS: "unable_to_connect"}, + ) + + return self.async_create_entry( + title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", data=user_input + ) + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + if not user_input: + return self.async_show_form( + step_id="user", data_schema=self.pick_integration_type_schema + ) + + if user_input["type"] == INTEGRATION_TYPE_GEOGRAPHY: + return await self.async_step_geography() + return await self.async_step_node_pro() + class AirVisualOptionsFlowHandler(config_entries.OptionsFlow): """Handle an AirVisual options flow.""" diff --git a/homeassistant/components/airvisual/const.py b/homeassistant/components/airvisual/const.py index ab54e191116..482c4191480 100644 --- a/homeassistant/components/airvisual/const.py +++ b/homeassistant/components/airvisual/const.py @@ -1,7 +1,11 @@ """Define AirVisual constants.""" -from datetime import timedelta +import logging DOMAIN = "airvisual" +LOGGER = logging.getLogger(__package__) + +INTEGRATION_TYPE_GEOGRAPHY = "Geographical Location" +INTEGRATION_TYPE_NODE_PRO = "AirVisual Node/Pro" CONF_CITY = "city" CONF_COUNTRY = "country" @@ -9,6 +13,4 @@ CONF_GEOGRAPHIES = "geographies" DATA_CLIENT = "client" -DEFAULT_SCAN_INTERVAL = timedelta(minutes=10) - -TOPIC_UPDATE = f"{DOMAIN}_update" +TOPIC_UPDATE = f"airvisual_update_{0}" diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index d5c7dc6853d..d97fcfb78ef 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -3,6 +3,6 @@ "name": "AirVisual", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airvisual", - "requirements": ["pyairvisual==3.0.1"], + "requirements": ["pyairvisual==4.3.0"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 20e76bf86b6..5009788e6fa 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -2,7 +2,6 @@ from logging import getLogger from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_STATE, @@ -13,12 +12,22 @@ from homeassistant.const import ( CONF_LONGITUDE, CONF_SHOW_ON_MAP, CONF_STATE, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + TEMP_CELSIUS, + UNIT_PERCENTAGE, ) from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity -from .const import CONF_CITY, CONF_COUNTRY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE +from . import AirVisualEntity +from .const import ( + CONF_CITY, + CONF_COUNTRY, + DATA_CLIENT, + DOMAIN, + INTEGRATION_TYPE_GEOGRAPHY, +) _LOGGER = getLogger(__name__) @@ -28,8 +37,6 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol" ATTR_POLLUTANT_UNIT = "pollutant_unit" ATTR_REGION = "region" -DEFAULT_ATTRIBUTION = "Data provided by AirVisual" - MASS_PARTS_PER_MILLION = "ppm" MASS_PARTS_PER_BILLION = "ppb" VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3" @@ -37,11 +44,22 @@ VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3" SENSOR_KIND_LEVEL = "air_pollution_level" SENSOR_KIND_AQI = "air_quality_index" SENSOR_KIND_POLLUTANT = "main_pollutant" -SENSORS = [ +SENSOR_KIND_BATTERY_LEVEL = "battery_level" +SENSOR_KIND_HUMIDITY = "humidity" +SENSOR_KIND_TEMPERATURE = "temperature" + +GEOGRAPHY_SENSORS = [ (SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None), (SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"), (SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None), ] +GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."} + +NODE_PRO_SENSORS = [ + (SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE), + (SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, UNIT_PERCENTAGE), + (SENSOR_KIND_TEMPERATURE, "Temperature", DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS), +] POLLUTANT_LEVEL_MAPPING = [ {"label": "Good", "icon": "mdi:emoticon-excited", "minimum": 0, "maximum": 50}, @@ -71,44 +89,64 @@ POLLUTANT_MAPPING = { "s2": {"label": "Sulfur Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION}, } -SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."} - -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up AirVisual sensors based on a config entry.""" - airvisual = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - async_add_entities( - [ - AirVisualSensor(airvisual, kind, name, icon, unit, locale, geography_id) + if airvisual.integration_type == INTEGRATION_TYPE_GEOGRAPHY: + sensors = [ + AirVisualGeographySensor( + airvisual, kind, name, icon, unit, locale, geography_id, + ) for geography_id in airvisual.data - for locale in SENSOR_LOCALES - for kind, name, icon, unit in SENSORS - ], - True, - ) + for locale in GEOGRAPHY_SENSOR_LOCALES + for kind, name, icon, unit in GEOGRAPHY_SENSORS + ] + else: + sensors = [ + AirVisualNodeProSensor(airvisual, kind, name, device_class, unit) + for kind, name, device_class, unit in NODE_PRO_SENSORS + ] + + async_add_entities(sensors, True) -class AirVisualSensor(Entity): - """Define an AirVisual sensor.""" +class AirVisualSensor(AirVisualEntity): + """Define a generic AirVisual sensor.""" - def __init__(self, airvisual, kind, name, icon, unit, locale, geography_id): + def __init__(self, airvisual, kind, name, unit): """Initialize.""" - self._airvisual = airvisual - self._geography_id = geography_id - self._icon = icon + super().__init__(airvisual) + self._kind = kind - self._locale = locale self._name = name self._state = None self._unit = unit - self._attrs = { - ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION, - ATTR_CITY: airvisual.data[geography_id].get(CONF_CITY), - ATTR_STATE: airvisual.data[geography_id].get(CONF_STATE), - ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY), - } + @property + def state(self): + """Return the state.""" + return self._state + + +class AirVisualGeographySensor(AirVisualSensor): + """Define an AirVisual sensor related to geography data via the Cloud API.""" + + def __init__(self, airvisual, kind, name, icon, unit, locale, geography_id): + """Initialize.""" + super().__init__(airvisual, kind, name, unit) + + self._attrs.update( + { + ATTR_CITY: airvisual.data[geography_id].get(CONF_CITY), + ATTR_STATE: airvisual.data[geography_id].get(CONF_STATE), + ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY), + } + ) + self._geography_id = geography_id + self._icon = icon + self._locale = locale @property def available(self): @@ -120,47 +158,18 @@ class AirVisualSensor(Entity): except KeyError: return False - @property - def device_state_attributes(self): - """Return the device state attributes.""" - return self._attrs - - @property - def icon(self): - """Return the icon.""" - return self._icon - @property def name(self): """Return the name.""" - return f"{SENSOR_LOCALES[self._locale]} {self._name}" - - @property - def state(self): - """Return the state.""" - return self._state + return f"{GEOGRAPHY_SENSOR_LOCALES[self._locale]} {self._name}" @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" return f"{self._geography_id}_{self._locale}_{self._kind}" - @property - def unit_of_measurement(self): - """Return the unit the value is expressed in.""" - return self._unit - - async def async_added_to_hass(self): - """Register callbacks.""" - - @callback - def update(): - """Update the state.""" - self.async_schedule_update_ha_state(True) - - self.async_on_remove(async_dispatcher_connect(self.hass, TOPIC_UPDATE, update)) - - async def async_update(self): + @callback + def update_from_latest_data(self): """Update the sensor.""" try: data = self._airvisual.data[self._geography_id]["current"]["pollution"] @@ -203,3 +212,62 @@ class AirVisualSensor(Entity): self._attrs["long"] = self._airvisual.geography_data[CONF_LONGITUDE] self._attrs.pop(ATTR_LATITUDE, None) self._attrs.pop(ATTR_LONGITUDE, None) + + +class AirVisualNodeProSensor(AirVisualSensor): + """Define an AirVisual sensor related to a Node/Pro unit.""" + + def __init__(self, airvisual, kind, name, device_class, unit): + """Initialize.""" + super().__init__(airvisual, kind, name, unit) + + self._device_class = device_class + + @property + def available(self): + """Return True if entity is available.""" + return bool(self._airvisual.data) + + @property + def device_class(self): + """Return the device class.""" + return self._device_class + + @property + def device_info(self): + """Return device registry information for this entity.""" + return { + "identifiers": {(DOMAIN, self._airvisual.data["current"]["serial_number"])}, + "name": self._airvisual.data["current"]["settings"]["node_name"], + "manufacturer": "AirVisual", + "model": f'{self._airvisual.data["current"]["status"]["model"]}', + "sw_version": ( + f'Version {self._airvisual.data["current"]["status"]["system_version"]}' + f'{self._airvisual.data["current"]["status"]["app_version"]}' + ), + } + + @property + def name(self): + """Return the name.""" + node_name = self._airvisual.data["current"]["settings"]["node_name"] + return f"{node_name} Node/Pro: {self._name}" + + @property + def unique_id(self): + """Return a unique, Home Assistant friendly identifier for this entity.""" + return f"{self._airvisual.data['current']['serial_number']}_{self._kind}" + + @callback + def update_from_latest_data(self): + """Update from the Node/Pro's data.""" + if self._kind == SENSOR_KIND_BATTERY_LEVEL: + self._state = self._airvisual.data["current"]["status"]["battery"] + elif self._kind == SENSOR_KIND_HUMIDITY: + self._state = self._airvisual.data["current"]["measurements"].get( + "humidity" + ) + elif self._kind == SENSOR_KIND_TEMPERATURE: + self._state = self._airvisual.data["current"]["measurements"].get( + "temperature_C" + ) diff --git a/homeassistant/components/airvisual/strings.json b/homeassistant/components/airvisual/strings.json index cd81d1862dd..8b9978b611f 100644 --- a/homeassistant/components/airvisual/strings.json +++ b/homeassistant/components/airvisual/strings.json @@ -1,27 +1,49 @@ { "config": { "step": { - "user": { - "title": "Configure AirVisual", - "description": "Monitor air quality in a geographical location.", + "geography": { + "title": "Configure a Geography", + "description": "Use the AirVisual cloud API to monitor a geographical location.", "data": { "api_key": "API Key", "latitude": "Latitude", "longitude": "Longitude" } + }, + "node_pro": { + "title": "Configure an AirVisual Node/Pro", + "description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.", + "data": { + "ip_address": "Unit IP Address/Hostname", + "password": "Unit Password" + } + }, + "user": { + "title": "Configure AirVisual", + "description": "Pick what type of AirVisual data you want to monitor.", + "data": { + "cloud_api": "Geographical Location", + "node_pro": "AirVisual Node Pro", + "type": "Integration Type" + } } }, - "error": { "invalid_api_key": "Invalid API key" }, + "error": { + "general_error": "There was an unknown error.", + "invalid_api_key": "Invalid API key provided.", + "unable_to_connect": "Unable to connect to Node/Pro unit." + }, "abort": { - "already_configured": "These coordinates have already been registered." + "already_configured": "These coordinates or Node/Pro ID are already registered." } }, "options": { "step": { "init": { "title": "Configure AirVisual", - "description": "Set various options for the AirVisual integration.", - "data": { "show_on_map": "Show monitored geography on the map" } + "data": { + "show_on_map": "Show monitored geography on the map" + } } } } diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index c7f2c32b4e3..b32ba9ec1ab 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -1,19 +1,38 @@ { "config": { "abort": { - "already_configured": "These coordinates have already been registered." + "already_configured": "These coordinates or Node/Pro ID are already registered." }, "error": { - "invalid_api_key": "Invalid API key" + "general_error": "There was an unknown error.", + "invalid_api_key": "Invalid API key provided.", + "unable_to_connect": "Unable to connect to Node/Pro unit." }, "step": { - "user": { + "geography": { "data": { "api_key": "API Key", "latitude": "Latitude", "longitude": "Longitude" }, - "description": "Monitor air quality in a geographical location.", + "description": "Use the AirVisual cloud API to monitor a geographical location.", + "title": "Configure a Geography" + }, + "node_pro": { + "data": { + "ip_address": "Unit IP Address/Hostname", + "password": "Unit Password" + }, + "description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.", + "title": "Configure an AirVisual Node/Pro" + }, + "user": { + "data": { + "cloud_api": "Geographical Location", + "node_pro": "AirVisual Node Pro", + "type": "Integration Type" + }, + "description": "Pick what type of AirVisual data you want to monitor.", "title": "Configure AirVisual" } } @@ -24,7 +43,6 @@ "data": { "show_on_map": "Show monitored geography on the map" }, - "description": "Set various options for the AirVisual integration.", "title": "Configure AirVisual" } } diff --git a/requirements_all.txt b/requirements_all.txt index 8c89391d060..faeaa3e0036 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1173,7 +1173,7 @@ pyaehw4a1==0.3.4 pyaftership==0.1.2 # homeassistant.components.airvisual -pyairvisual==3.0.1 +pyairvisual==4.3.0 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77c12ead2c7..2509a3d3935 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -470,7 +470,7 @@ py_nextbusnext==0.1.4 pyaehw4a1==0.3.4 # homeassistant.components.airvisual -pyairvisual==3.0.1 +pyairvisual==4.3.0 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index d21aec14fa0..57852969d71 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -1,14 +1,21 @@ """Define tests for the AirVisual config flow.""" from asynctest import patch -from pyairvisual.errors import InvalidKeyError +from pyairvisual.errors import InvalidKeyError, NodeProError from homeassistant import data_entry_flow -from homeassistant.components.airvisual import CONF_GEOGRAPHIES, DOMAIN +from homeassistant.components.airvisual import ( + CONF_GEOGRAPHIES, + DOMAIN, + INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_NODE_PRO, +) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import ( CONF_API_KEY, + CONF_IP_ADDRESS, CONF_LATITUDE, CONF_LONGITUDE, + CONF_PASSWORD, CONF_SHOW_ON_MAP, ) from homeassistant.setup import async_setup_component @@ -17,28 +24,43 @@ from tests.common import MockConfigEntry async def test_duplicate_error(hass): - """Test that errors are shown when duplicates are added.""" - conf = { + """Test that errors are shown when duplicate entries are added.""" + geography_conf = { CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, } + node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "12345"} MockConfigEntry( - domain=DOMAIN, unique_id="51.528308, -0.3817765", data=conf + domain=DOMAIN, unique_id="51.528308, -0.3817765", data=geography_conf ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": SOURCE_IMPORT}, data=geography_conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + MockConfigEntry( + domain=DOMAIN, unique_id="192.168.1.100", data=node_pro_conf + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=node_pro_conf ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" -async def test_invalid_api_key(hass): - """Test that invalid credentials throws an error.""" - conf = { +async def test_invalid_identifier(hass): + """Test that an invalid API key or Node/Pro ID throws an error.""" + geography_conf = { CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, @@ -48,11 +70,29 @@ async def test_invalid_api_key(hass): "pyairvisual.api.API.nearest_city", side_effect=InvalidKeyError, ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf + DOMAIN, context={"source": SOURCE_IMPORT}, data=geography_conf ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} +async def test_node_pro_error(hass): + """Test that an invalid Node/Pro ID shows an error.""" + node_pro_conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"} + + with patch( + "pyairvisual.node.Node.from_samba", side_effect=NodeProError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=node_pro_conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_IP_ADDRESS: "unable_to_connect"} + + async def test_migration_1_2(hass): """Test migrating from version 1 to version 2.""" conf = { @@ -96,12 +136,16 @@ async def test_migration_1_2(hass): async def test_options_flow(hass): """Test config flow options.""" - conf = {CONF_API_KEY: "abcde12345"} + geography_conf = { + CONF_API_KEY: "abcde12345", + CONF_LATITUDE: 51.528308, + CONF_LONGITUDE: -0.3817765, + } config_entry = MockConfigEntry( domain=DOMAIN, - unique_id="abcde12345", - data=conf, + unique_id="51.528308, -0.3817765", + data=geography_conf, options={CONF_SHOW_ON_MAP: True}, ) config_entry.add_to_hass(hass) @@ -122,18 +166,8 @@ async def test_options_flow(hass): assert config_entry.options == {CONF_SHOW_ON_MAP: False} -async def test_show_form(hass): - """Test that the form is served with no input.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - -async def test_step_import(hass): - """Test that the import step works.""" +async def test_step_geography(hass): + """Test the geograph (cloud API) step.""" conf = { CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, @@ -146,6 +180,50 @@ async def test_step_import(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_IMPORT}, data=conf ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Cloud API (51.528308, -0.3817765)" + assert result["data"] == { + CONF_API_KEY: "abcde12345", + CONF_LATITUDE: 51.528308, + CONF_LONGITUDE: -0.3817765, + } + + +async def test_step_node_pro(hass): + """Test the Node/Pro step.""" + conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password"} + + with patch( + "homeassistant.components.airvisual.async_setup_entry", return_value=True + ), patch("pyairvisual.node.Node.from_samba"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={"type": "AirVisual Node/Pro"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "Node/Pro (192.168.1.100)" + assert result["data"] == { + CONF_IP_ADDRESS: "192.168.1.100", + CONF_PASSWORD: "my_password", + } + + +async def test_step_import(hass): + """Test the import step for both types of configuration.""" + geography_conf = { + CONF_API_KEY: "abcde12345", + CONF_LATITUDE: 51.528308, + CONF_LONGITUDE: -0.3817765, + } + + with patch( + "homeassistant.components.airvisual.async_setup_entry", return_value=True + ), patch("pyairvisual.api.API.nearest_city"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=geography_conf + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Cloud API (51.528308, -0.3817765)" @@ -157,23 +235,28 @@ async def test_step_import(hass): async def test_step_user(hass): - """Test that the user step works.""" - conf = { - CONF_API_KEY: "abcde12345", - CONF_LATITUDE: 32.87336, - CONF_LONGITUDE: -117.22743, - } + """Test the user ("pick the integration type") step.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) - with patch( - "homeassistant.components.airvisual.async_setup_entry", return_value=True - ), patch("pyairvisual.api.API.nearest_city"): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "Cloud API (32.87336, -117.22743)" - assert result["data"] == { - CONF_API_KEY: "abcde12345", - CONF_LATITUDE: 32.87336, - CONF_LONGITUDE: -117.22743, - } + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_GEOGRAPHY}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "geography" + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data={"type": INTEGRATION_TYPE_NODE_PRO}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "node_pro" From da87ec8499702380b6ecccb8fbaf2aa2d697bbe6 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 23 Apr 2020 00:05:07 +0000 Subject: [PATCH 009/511] [ci skip] Translation update --- .../components/airvisual/translations/en.json | 4 ++ .../components/atag/translations/ca.json | 20 +++++++++ .../components/atag/translations/en.json | 20 +++++++++ .../components/atag/translations/fr.json | 12 ++++++ .../components/august/translations/pt.json | 12 ++++++ .../components/axis/translations/pt.json | 3 +- .../binary_sensor/translations/en.json | 4 +- .../binary_sensor/translations/pt.json | 43 +++++++++++++++++++ .../binary_sensor/translations/zh-Hant.json | 4 +- .../components/brother/translations/pt.json | 11 +++++ .../configurator/translations/pl.json | 2 +- .../components/deconz/translations/nl.json | 6 +++ .../components/deconz/translations/pt.json | 25 ++++++++++- .../components/doorbird/translations/pt.json | 17 ++++++++ .../components/elgato/translations/pt.json | 12 ++++++ .../components/elkm1/translations/pt.json | 12 ++++++ .../components/esphome/translations/hu.json | 2 +- .../components/flume/translations/pt.json | 12 ++++++ .../flunearyou/translations/pt.json | 12 ++++++ .../components/fritzbox/translations/fr.json | 21 +++++++++ .../components/fritzbox/translations/nl.json | 21 +++++++++ .../components/fritzbox/translations/pt.json | 21 +++++++++ .../garmin_connect/translations/pt.json | 12 ++++++ .../components/gdacs/translations/pt.json | 11 +++++ .../geonetnz_volcano/translations/pt.json | 11 +++++ .../components/group/translations/en.json | 4 +- .../components/group/translations/ru.json | 4 +- .../group/translations/zh-Hant.json | 4 +- .../homekit_controller/translations/hu.json | 2 +- .../huawei_lte/translations/pt.json | 3 +- .../components/icloud/translations/pt.json | 17 ++++++++ .../media_player/translations/pt.json | 8 ++++ .../media_player/translations/ru.json | 4 +- .../components/melcloud/translations/pt.json | 11 +++++ .../meteo_france/translations/pt.json | 12 ++++++ .../components/mikrotik/translations/pt.json | 12 ++++++ .../minecraft_server/translations/pt.json | 11 +++++ .../components/monoprice/translations/pl.json | 25 +++++++++++ .../components/mqtt/translations/pt.json | 2 +- .../components/myq/translations/pl.json | 21 +++++++++ .../components/myq/translations/pt.json | 12 ++++++ .../components/nexia/translations/pl.json | 21 +++++++++ .../components/nexia/translations/pt.json | 12 ++++++ .../components/notion/translations/pt.json | 3 +- .../components/nuheat/translations/pt.json | 12 ++++++ .../components/nut/translations/pt.json | 19 ++++++++ .../components/nws/translations/nl.json | 11 +++++ .../onboarding/translations/pt.json | 7 +++ .../panasonic_viera/translations/fr.json | 13 ++++++ .../panasonic_viera/translations/nl.json | 21 +++++++++ .../panasonic_viera/translations/pt.json | 10 +++++ .../components/plex/translations/pt.json | 14 +----- .../components/ring/translations/pt.json | 12 ++++++ .../components/roomba/translations/fr.json | 38 ++++------------ .../components/samsungtv/translations/pt.json | 11 +++++ .../season/translations/sensor.fr.json | 6 +++ .../season/translations/sensor.nl.json | 6 +++ .../components/sense/translations/pt.json | 11 +++++ .../simplisafe/translations/pt.json | 2 +- .../smartthings/translations/nl.json | 6 +++ .../components/starline/translations/pt.json | 28 ++++++++++++ .../synology_dsm/translations/ca.json | 4 +- .../synology_dsm/translations/en.json | 2 + .../synology_dsm/translations/nl.json | 5 +++ .../synology_dsm/translations/pt.json | 19 +++++++- .../components/tado/translations/pt.json | 12 ++++++ .../components/tesla/translations/pt.json | 12 ++++++ .../components/timer/translations/hu.json | 6 +-- .../transmission/translations/pt.json | 3 +- .../components/unifi/translations/pt.json | 9 +++- .../components/upnp/translations/en.json | 2 +- .../components/vacuum/translations/hu.json | 2 +- .../components/vesync/translations/pt.json | 2 +- .../components/vizio/translations/pt.json | 11 +++++ .../components/wled/translations/hu.json | 16 +++---- .../components/zha/translations/pt.json | 10 +++++ .../components/zwave/translations/hu.json | 4 +- .../components/zwave/translations/pl.json | 4 +- 78 files changed, 782 insertions(+), 86 deletions(-) create mode 100644 homeassistant/components/atag/translations/ca.json create mode 100644 homeassistant/components/atag/translations/en.json create mode 100644 homeassistant/components/atag/translations/fr.json create mode 100644 homeassistant/components/august/translations/pt.json create mode 100644 homeassistant/components/brother/translations/pt.json create mode 100644 homeassistant/components/doorbird/translations/pt.json create mode 100644 homeassistant/components/elgato/translations/pt.json create mode 100644 homeassistant/components/elkm1/translations/pt.json create mode 100644 homeassistant/components/flume/translations/pt.json create mode 100644 homeassistant/components/flunearyou/translations/pt.json create mode 100644 homeassistant/components/fritzbox/translations/fr.json create mode 100644 homeassistant/components/fritzbox/translations/nl.json create mode 100644 homeassistant/components/fritzbox/translations/pt.json create mode 100644 homeassistant/components/garmin_connect/translations/pt.json create mode 100644 homeassistant/components/gdacs/translations/pt.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/pt.json create mode 100644 homeassistant/components/icloud/translations/pt.json create mode 100644 homeassistant/components/melcloud/translations/pt.json create mode 100644 homeassistant/components/meteo_france/translations/pt.json create mode 100644 homeassistant/components/mikrotik/translations/pt.json create mode 100644 homeassistant/components/minecraft_server/translations/pt.json create mode 100644 homeassistant/components/monoprice/translations/pl.json create mode 100644 homeassistant/components/myq/translations/pl.json create mode 100644 homeassistant/components/myq/translations/pt.json create mode 100644 homeassistant/components/nexia/translations/pl.json create mode 100644 homeassistant/components/nexia/translations/pt.json create mode 100644 homeassistant/components/nuheat/translations/pt.json create mode 100644 homeassistant/components/nut/translations/pt.json create mode 100644 homeassistant/components/nws/translations/nl.json create mode 100644 homeassistant/components/onboarding/translations/pt.json create mode 100644 homeassistant/components/panasonic_viera/translations/fr.json create mode 100644 homeassistant/components/panasonic_viera/translations/nl.json create mode 100644 homeassistant/components/ring/translations/pt.json create mode 100644 homeassistant/components/samsungtv/translations/pt.json create mode 100644 homeassistant/components/sense/translations/pt.json create mode 100644 homeassistant/components/starline/translations/pt.json create mode 100644 homeassistant/components/tado/translations/pt.json create mode 100644 homeassistant/components/tesla/translations/pt.json create mode 100644 homeassistant/components/vizio/translations/pt.json diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index b32ba9ec1ab..842eaaaa1de 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -28,7 +28,10 @@ }, "user": { "data": { + "api_key": "API Key", "cloud_api": "Geographical Location", + "latitude": "Latitude", + "longitude": "Longitude", "node_pro": "AirVisual Node Pro", "type": "Integration Type" }, @@ -43,6 +46,7 @@ "data": { "show_on_map": "Show monitored geography on the map" }, + "description": "Set various options for the AirVisual integration.", "title": "Configure AirVisual" } } diff --git a/homeassistant/components/atag/translations/ca.json b/homeassistant/components/atag/translations/ca.json new file mode 100644 index 00000000000..994cc3c8fbe --- /dev/null +++ b/homeassistant/components/atag/translations/ca.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Nom\u00e9s es pot afegir un sol dispositiu Atag a Home Assistant" + }, + "error": { + "connection_error": "No s'ha pogut connectar, torna-ho a provar" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port (10000)" + }, + "title": "Connexi\u00f3 amb el dispositiu" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/en.json b/homeassistant/components/atag/translations/en.json new file mode 100644 index 00000000000..edee94a8e04 --- /dev/null +++ b/homeassistant/components/atag/translations/en.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Only one Atag device can be added to Home Assistant" + }, + "error": { + "connection_error": "Failed to connect, please try again" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port (10000)" + }, + "title": "Connect to the device" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/fr.json b/homeassistant/components/atag/translations/fr.json new file mode 100644 index 00000000000..26b88fe4d20 --- /dev/null +++ b/homeassistant/components/atag/translations/fr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port (10000)" + }, + "title": "Se connecter \u00e0 l'appareil" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/pt.json b/homeassistant/components/august/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/august/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pt.json b/homeassistant/components/axis/translations/pt.json index 77ce7025f70..2dc5a14249f 100644 --- a/homeassistant/components/axis/translations/pt.json +++ b/homeassistant/components/axis/translations/pt.json @@ -10,5 +10,6 @@ } } } - } + }, + "title": "Dispositivo Axis" } \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/en.json b/homeassistant/components/binary_sensor/translations/en.json index 300fad7261f..c9a1ad15a8b 100644 --- a/homeassistant/components/binary_sensor/translations/en.json +++ b/homeassistant/components/binary_sensor/translations/en.json @@ -143,8 +143,8 @@ "on": "Open" }, "presence": { - "off": "[%key:component::device_tracker::state::not_home%]", - "on": "[%key:component::device_tracker::state::home%]" + "off": "Away", + "on": "Home" }, "problem": { "off": "OK", diff --git a/homeassistant/components/binary_sensor/translations/pt.json b/homeassistant/components/binary_sensor/translations/pt.json index 3942e8eff51..c71f43eca72 100644 --- a/homeassistant/components/binary_sensor/translations/pt.json +++ b/homeassistant/components/binary_sensor/translations/pt.json @@ -11,13 +11,56 @@ "is_moist": "{entity_name} est\u00e1 h\u00famido", "is_motion": "{entity_name} est\u00e1 a detectar movimento", "is_moving": "{entity_name} est\u00e1 a mexer", + "is_no_gas": "{entity_name} n\u00e3o est\u00e1 a detectar g\u00e1s", + "is_no_light": "{entity_name} n\u00e3o est\u00e1 a detectar a luz", + "is_no_motion": "{entity_name} n\u00e3o est\u00e1 a detectar movimento", + "is_no_problem": "{entity_name} n\u00e3o est\u00e1 a detectar o problema", + "is_no_smoke": "{entity_name} n\u00e3o est\u00e1 a detectar fumo", + "is_no_sound": "{entity_name} n\u00e3o est\u00e1 a detectar som", + "is_no_vibration": "{entity_name} n\u00e3o est\u00e1 a detectar vibra\u00e7\u00f5es", + "is_not_bat_low": "{entity_name} a bateria est\u00e1 normal", + "is_not_cold": "{entity_name} n\u00e3o est\u00e1 frio", + "is_not_connected": "{entity_name} est\u00e1 desligado", + "is_not_hot": "{entity_name} n\u00e3o est\u00e1 quente", + "is_not_locked": "{entity_name} est\u00e1 destrancado", + "is_not_moist": "{entity_name} est\u00e1 seco", + "is_not_moving": "{entity_name} n\u00e3o est\u00e1 a mexer", + "is_not_occupied": "{entity_name} n\u00e3o est\u00e1 ocupado", "is_not_open": "{entity_name} est\u00e1 fechada", + "is_not_plugged_in": "{entity_name} est\u00e1 desconectado", + "is_not_powered": "{entity_name} n\u00e3o est\u00e1 alimentado", + "is_not_present": "{entity_name} n\u00e3o est\u00e1 presente", + "is_not_unsafe": "{entity_name} est\u00e1 seguro", + "is_occupied": "{entity_name} est\u00e1 ocupado", "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado", + "is_open": "{entity_name} est\u00e1 aberto", + "is_plugged_in": "{entity_name} est\u00e1 conectado", + "is_powered": "{entity_name} est\u00e1 alimentado", + "is_present": "{entity_name} est\u00e1 presente", + "is_problem": "{entity_name} est\u00e1 a detectar um problema", + "is_smoke": "{entity_name} est\u00e1 a detectar fumo", + "is_sound": "{entity_name} est\u00e1 a detectar som", "is_vibration": "{entity_name} est\u00e1 a detectar vibra\u00e7\u00f5es" }, "trigger_type": { "moist": "ficou h\u00famido {entity_name}", + "moving": "{entity_name} come\u00e7ou a mover-se", + "no_gas": "{entity_name} deixou de detectar g\u00e1s", + "no_light": "{entity_name} deixou de detectar luz", + "no_motion": "{entity_name} deixou de detectar movimento", + "no_problem": "{entity_name} deixou de detectar problemas", + "no_smoke": "{entity_name} deixou de detectar fumo", + "no_sound": "{entity_name} deixou de detectar som", + "no_vibration": "{entity_name} deixou de detectar vibra\u00e7\u00e3o", + "not_bat_low": "{entity_name} bateria normal", + "not_cold": "{entity_name} deixou de estar frio", + "not_connected": "{entity_name} est\u00e1 desligado", + "not_hot": "{entity_name} deixou de estar quente", + "not_locked": "{entity_name} destrancado", + "not_moist": "{entity_name} ficou seco", + "not_moving": "{entity_name} deixou de se mover", + "not_occupied": "{entity_name} deixou de estar ocupado", "not_opened": "fechado {entity_name}", "not_plugged_in": "{entity_name} desligado", "not_powered": "{entity_name} n\u00e3o alimentado", diff --git a/homeassistant/components/binary_sensor/translations/zh-Hant.json b/homeassistant/components/binary_sensor/translations/zh-Hant.json index 89307b4c1ff..a78ecdcf8ef 100644 --- a/homeassistant/components/binary_sensor/translations/zh-Hant.json +++ b/homeassistant/components/binary_sensor/translations/zh-Hant.json @@ -143,8 +143,8 @@ "on": "\u958b\u555f" }, "presence": { - "off": "[%key:component::device_tracker::state::not_home%]", - "on": "[%key:component::device_tracker::state::home%]" + "off": "\u96e2\u5bb6", + "on": "\u5728\u5bb6" }, "problem": { "off": "\u78ba\u5b9a", diff --git a/homeassistant/components/brother/translations/pt.json b/homeassistant/components/brother/translations/pt.json new file mode 100644 index 00000000000..4f76c66c4f6 --- /dev/null +++ b/homeassistant/components/brother/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "type": "Tipo de impressora" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/configurator/translations/pl.json b/homeassistant/components/configurator/translations/pl.json index 98a82bd80ae..45e5af46722 100644 --- a/homeassistant/components/configurator/translations/pl.json +++ b/homeassistant/components/configurator/translations/pl.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Skonfiguruj", + "configure": "skonfiguruj", "configured": "skonfigurowany" } }, diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index a139a4b9975..e1a9583e03b 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -29,6 +29,12 @@ "host": "Host", "port": "Poort" } + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Poort" + } } } }, diff --git a/homeassistant/components/deconz/translations/pt.json b/homeassistant/components/deconz/translations/pt.json index 0cd41e650f9..b385af86ce5 100644 --- a/homeassistant/components/deconz/translations/pt.json +++ b/homeassistant/components/deconz/translations/pt.json @@ -21,18 +21,41 @@ "host": "Servidor", "port": "Porta" } + }, + "manual_input": { + "data": { + "host": "Servidor", + "port": "Porta" + } } } }, "device_automation": { "trigger_subtype": { + "both_buttons": "Ambos os bot\u00f5es", + "bottom_buttons": "Bot\u00f5es inferiores", + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "close": "Fechar", + "dim_down": "Escurecer", + "dim_up": "Clariar", "left": "Esquerda", + "open": "Abrir", + "right": "Direita", "side_1": "Lado 1", "side_2": "Lado 2", "side_3": "Lado 3", "side_4": "Lado 4", "side_5": "Lado 5", - "side_6": "Lado 6" + "side_6": "Lado 6", + "top_buttons": "Bot\u00f5es superiores", + "turn_off": "Desligar", + "turn_on": "Ligar" + }, + "trigger_type": { + "remote_falling": "Dispositivo em queda livre" } } } \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/pt.json b/homeassistant/components/doorbird/translations/pt.json new file mode 100644 index 00000000000..6515658d6a7 --- /dev/null +++ b/homeassistant/components/doorbird/translations/pt.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "name": "Nome do dispositivo", + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/pt.json b/homeassistant/components/elgato/translations/pt.json new file mode 100644 index 00000000000..89c332cea25 --- /dev/null +++ b/homeassistant/components/elgato/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Nome servidor ou endere\u00e7o IP", + "port": "N\u00famero da porta" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/pt.json b/homeassistant/components/elkm1/translations/pt.json new file mode 100644 index 00000000000..83e574aa2e2 --- /dev/null +++ b/homeassistant/components/elkm1/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe (segura apenas)", + "username": "Nome de utilizador (apenas seguro)." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/hu.json b/homeassistant/components/esphome/translations/hu.json index b023bd03cbe..a178860d420 100644 --- a/homeassistant/components/esphome/translations/hu.json +++ b/homeassistant/components/esphome/translations/hu.json @@ -27,7 +27,7 @@ "port": "Port" }, "description": "K\u00e9rlek, add meg az [ESPHome](https://esphomelib.com/) csom\u00f3pontod kapcsol\u00f3d\u00e1si be\u00e1ll\u00edt\u00e1sait.", - "title": "[%key:component::esphome::title%]" + "title": "ESPHome" } } } diff --git a/homeassistant/components/flume/translations/pt.json b/homeassistant/components/flume/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/flume/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/pt.json b/homeassistant/components/flunearyou/translations/pt.json new file mode 100644 index 00000000000..c7081cd694a --- /dev/null +++ b/homeassistant/components/flunearyou/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json new file mode 100644 index 00000000000..8c62d2806c3 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "auth_failed": "Le nom d'utilisateur et / ou le mot de passe sont incorrects." + }, + "step": { + "confirm": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + }, + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json new file mode 100644 index 00000000000..4617a07a5b6 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "auth_failed": "Ongeldige gebruikersnaam of wachtwoord" + }, + "step": { + "confirm": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + }, + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/pt.json b/homeassistant/components/fritzbox/translations/pt.json new file mode 100644 index 00000000000..bd20deada06 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/pt.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "auth_failed": "Nome de utilizador ou palavra passe incorretos" + }, + "step": { + "confirm": { + "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/garmin_connect/translations/pt.json b/homeassistant/components/garmin_connect/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/pt.json b/homeassistant/components/gdacs/translations/pt.json new file mode 100644 index 00000000000..98180e11248 --- /dev/null +++ b/homeassistant/components/gdacs/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "radius": "Raio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/pt.json b/homeassistant/components/geonetnz_volcano/translations/pt.json new file mode 100644 index 00000000000..98180e11248 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "radius": "Raio" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/group/translations/en.json b/homeassistant/components/group/translations/en.json index 2f6ee5c2d40..271ada378ca 100644 --- a/homeassistant/components/group/translations/en.json +++ b/homeassistant/components/group/translations/en.json @@ -2,9 +2,9 @@ "state": { "_": { "closed": "Closed", - "home": "[%key:component::device_tracker::state::home%]", + "home": "Home", "locked": "Locked", - "not_home": "[%key:component::device_tracker::state::not_home%]", + "not_home": "Away", "off": "Off", "ok": "OK", "on": "On", diff --git a/homeassistant/components/group/translations/ru.json b/homeassistant/components/group/translations/ru.json index 01fac1581ca..7103b9f75d0 100644 --- a/homeassistant/components/group/translations/ru.json +++ b/homeassistant/components/group/translations/ru.json @@ -3,14 +3,14 @@ "_": { "closed": "\u0417\u0430\u043a\u0440\u044b\u0442\u043e", "home": "\u0414\u043e\u043c\u0430", - "locked": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0430", + "locked": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e", "not_home": "\u041d\u0435 \u0434\u043e\u043c\u0430", "off": "\u0412\u044b\u043a\u043b", "ok": "\u041e\u041a", "on": "\u0412\u043a\u043b", "open": "\u041e\u0442\u043a\u0440\u044b\u0442\u043e", "problem": "\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430", - "unlocked": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0430" + "unlocked": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e" } }, "title": "\u0413\u0440\u0443\u043f\u043f\u0430" diff --git a/homeassistant/components/group/translations/zh-Hant.json b/homeassistant/components/group/translations/zh-Hant.json index 6bf7487c041..790feb0c5ff 100644 --- a/homeassistant/components/group/translations/zh-Hant.json +++ b/homeassistant/components/group/translations/zh-Hant.json @@ -2,9 +2,9 @@ "state": { "_": { "closed": "\u95dc\u9589", - "home": "[%key:component::device_tracker::state::home%]", + "home": "\u5728\u5bb6", "locked": "\u5df2\u4e0a\u9396", - "not_home": "[%key:component::device_tracker::state::not_home%]", + "not_home": "\u96e2\u5bb6", "off": "\u95dc\u9589", "ok": "\u6b63\u5e38", "on": "\u958b\u555f", diff --git a/homeassistant/components/homekit_controller/translations/hu.json b/homeassistant/components/homekit_controller/translations/hu.json index 53645196388..c6d81d985bb 100644 --- a/homeassistant/components/homekit_controller/translations/hu.json +++ b/homeassistant/components/homekit_controller/translations/hu.json @@ -36,5 +36,5 @@ } } }, - "title": "HomeKit tartoz\u00e9k" + "title": "HomeKit Vez\u00e9rl\u0151" } \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/pt.json b/homeassistant/components/huawei_lte/translations/pt.json index a55c5933260..a71678deecc 100644 --- a/homeassistant/components/huawei_lte/translations/pt.json +++ b/homeassistant/components/huawei_lte/translations/pt.json @@ -5,6 +5,7 @@ "already_in_progress": "Este dispositivo j\u00e1 est\u00e1 a ser configurado" }, "error": { + "connection_timeout": "Liga\u00e7\u00e3o expirou", "incorrect_password": "Palavra-passe incorreta", "incorrect_username": "Nome de Utilizador incorreto", "incorrect_username_or_password": "Nome de utilizador ou palavra passe incorretos" @@ -14,7 +15,7 @@ "data": { "password": "Palavra-passe", "url": "", - "username": "Utilizador" + "username": "Nome do utilizador" }, "title": "Configurar o Huawei LTE" } diff --git a/homeassistant/components/icloud/translations/pt.json b/homeassistant/components/icloud/translations/pt.json new file mode 100644 index 00000000000..420196bb050 --- /dev/null +++ b/homeassistant/components/icloud/translations/pt.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "trusted_device": { + "data": { + "trusted_device": "Dispositivo confi\u00e1vel" + } + }, + "user": { + "data": { + "password": "Palavra-passe", + "username": "Email" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/pt.json b/homeassistant/components/media_player/translations/pt.json index 41399a76ad2..a3c741ce0e2 100644 --- a/homeassistant/components/media_player/translations/pt.json +++ b/homeassistant/components/media_player/translations/pt.json @@ -1,4 +1,12 @@ { + "device_automation": { + "condition_type": { + "is_off": "{entity_name} est\u00e1 desligada", + "is_on": "{entity_name} est\u00e1 ligada", + "is_paused": "{entity_name} est\u00e1 em pausa", + "is_playing": "{entity_name} est\u00e1 a reproduzir" + } + }, "state": { "_": { "idle": "Em espera", diff --git a/homeassistant/components/media_player/translations/ru.json b/homeassistant/components/media_player/translations/ru.json index 6f5a7cd5234..8ed46953675 100644 --- a/homeassistant/components/media_player/translations/ru.json +++ b/homeassistant/components/media_player/translations/ru.json @@ -11,8 +11,8 @@ "state": { "_": { "idle": "\u0411\u0435\u0437\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435", - "off": "\u0412\u044b\u043a\u043b", - "on": "\u0412\u043a\u043b", + "off": "\u0412\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u043e", + "on": "\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u043e", "paused": "\u041f\u0430\u0443\u0437\u0430", "playing": "\u0412\u043e\u0441\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0434\u0435\u043d\u0438\u0435", "standby": "\u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435" diff --git a/homeassistant/components/melcloud/translations/pt.json b/homeassistant/components/melcloud/translations/pt.json new file mode 100644 index 00000000000..c09e8c63d11 --- /dev/null +++ b/homeassistant/components/melcloud/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe MELCloud" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/pt.json b/homeassistant/components/meteo_france/translations/pt.json new file mode 100644 index 00000000000..025d58f5197 --- /dev/null +++ b/homeassistant/components/meteo_france/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "city": "Cidade" + }, + "title": "" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/pt.json b/homeassistant/components/mikrotik/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/pt.json b/homeassistant/components/minecraft_server/translations/pt.json new file mode 100644 index 00000000000..286cd58dd89 --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/pl.json b/homeassistant/components/monoprice/translations/pl.json new file mode 100644 index 00000000000..d0a3d1751a7 --- /dev/null +++ b/homeassistant/components/monoprice/translations/pl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "port": "Port szeregowy", + "source_1": "Nazwa \u017ar\u00f3d\u0142a #1", + "source_2": "Nazwa \u017ar\u00f3d\u0142a #2", + "source_3": "Nazwa \u017ar\u00f3d\u0142a #3", + "source_4": "Nazwa \u017ar\u00f3d\u0142a #4", + "source_5": "Nazwa \u017ar\u00f3d\u0142a #5", + "source_6": "Nazwa \u017ar\u00f3d\u0142a #6" + }, + "title": "Po\u0142\u0105cz z urz\u0105dzeniem" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/pt.json b/homeassistant/components/mqtt/translations/pt.json index 0776c148e90..ef17b291566 100644 --- a/homeassistant/components/mqtt/translations/pt.json +++ b/homeassistant/components/mqtt/translations/pt.json @@ -13,7 +13,7 @@ "discovery": "Ativar descoberta", "password": "Palavra-passe", "port": "Porto", - "username": "Utilizador" + "username": "Nome de Utilizador" }, "description": "Por favor, insira os detalhes de liga\u00e7\u00e3o ao seu broker MQTT.", "title": "MQTT" diff --git a/homeassistant/components/myq/translations/pl.json b/homeassistant/components/myq/translations/pl.json new file mode 100644 index 00000000000..74220a6518b --- /dev/null +++ b/homeassistant/components/myq/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "MyQ jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105czenie z bramk\u0105 MyQ" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/pt.json b/homeassistant/components/myq/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/myq/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/pl.json b/homeassistant/components/nexia/translations/pl.json new file mode 100644 index 00000000000..0819834fcc8 --- /dev/null +++ b/homeassistant/components/nexia/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "nexia jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105czenie z mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/pt.json b/homeassistant/components/nexia/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/nexia/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/pt.json b/homeassistant/components/notion/translations/pt.json index e379229ec3a..b09d2c55929 100644 --- a/homeassistant/components/notion/translations/pt.json +++ b/homeassistant/components/notion/translations/pt.json @@ -6,7 +6,8 @@ "step": { "user": { "data": { - "password": "Palavra-passe" + "password": "Palavra-passe", + "username": "Nome de utilizador / Endere\u00e7o de e-mail" } } } diff --git a/homeassistant/components/nuheat/translations/pt.json b/homeassistant/components/nuheat/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/nuheat/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt.json b/homeassistant/components/nut/translations/pt.json new file mode 100644 index 00000000000..4ebc88bb1cf --- /dev/null +++ b/homeassistant/components/nut/translations/pt.json @@ -0,0 +1,19 @@ +{ + "config": { + "step": { + "ups": { + "data": { + "resources": "Recursos" + } + }, + "user": { + "data": { + "host": "Servidor", + "password": "Palavra-passe", + "port": "Porta", + "username": "Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json new file mode 100644 index 00000000000..590b9c90e12 --- /dev/null +++ b/homeassistant/components/nws/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "longitude": "Lengtegraad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onboarding/translations/pt.json b/homeassistant/components/onboarding/translations/pt.json new file mode 100644 index 00000000000..d5a09a0b240 --- /dev/null +++ b/homeassistant/components/onboarding/translations/pt.json @@ -0,0 +1,7 @@ +{ + "area": { + "bedroom": "Quarto", + "kitchen": "Cozinha", + "living_room": "Sala de estar" + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/fr.json b/homeassistant/components/panasonic_viera/translations/fr.json new file mode 100644 index 00000000000..5cf966fc9e6 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/fr.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Adresse IP", + "name": "Nom" + }, + "title": "Configurer votre t\u00e9l\u00e9viseur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/nl.json b/homeassistant/components/panasonic_viera/translations/nl.json new file mode 100644 index 00000000000..79bc899c477 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "invalid_pin_code": "De ingevoerde pincode is ongeldig" + }, + "step": { + "pairing": { + "data": { + "pin": "PIN" + } + }, + "user": { + "data": { + "host": "IP-adres", + "name": "Naam" + }, + "title": "Uw tv instellen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/pt.json b/homeassistant/components/panasonic_viera/translations/pt.json index 0d9831f1dc8..e9d63f1114f 100644 --- a/homeassistant/components/panasonic_viera/translations/pt.json +++ b/homeassistant/components/panasonic_viera/translations/pt.json @@ -1,7 +1,17 @@ { "config": { "step": { + "pairing": { + "data": { + "pin": "PIN" + }, + "title": "Emparelhamento" + }, "user": { + "data": { + "host": "Endere\u00e7o IP", + "name": "Nome" + }, "title": "Configure a sua TV" } } diff --git a/homeassistant/components/plex/translations/pt.json b/homeassistant/components/plex/translations/pt.json index 4312910653f..ebf1df9c18f 100644 --- a/homeassistant/components/plex/translations/pt.json +++ b/homeassistant/components/plex/translations/pt.json @@ -1,19 +1,9 @@ { "config": { "step": { - "manual_setup": { + "select_server": { "data": { - "host": "Servidor", - "port": "Porta" - } - } - } - }, - "options": { - "step": { - "plex_mp_settings": { - "data": { - "show_all_controls": "Mostrar todos os controles" + "server": "Servidor" } } } diff --git a/homeassistant/components/ring/translations/pt.json b/homeassistant/components/ring/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/ring/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 5f59144727f..0151782bf1b 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,33 +1,11 @@ { - "config": { - "title": "iRobot Roomba", - "step": { - "user": { - "title": "Connexion au périphérique", - "description": "Actuellement la récupération du BLID et du mot de passe nécessite une procédure manuelle. Veuillez suivre les étapes décrites dans la documentation sur: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", - "data": { - "host": "Nom ou Addresse IP", - "blid": "BLID", - "password": "Mot de passe", - "certificate": "Certificat", - "continuous": "Continue", - "delay": "Delais" + "options": { + "step": { + "init": { + "data": { + "delay": "D\u00e9lai" + } + } } - } - }, - "error": { - "unknown" : "Erreur imprévu", - "cannot_connect": "Impossible de se connecter" } - }, - "options": { - "step": { - "init": { - "data": { - "continuous": "Continue", - "delay": "Delais" - } - } - } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/pt.json b/homeassistant/components/samsungtv/translations/pt.json new file mode 100644 index 00000000000..286cd58dd89 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.fr.json b/homeassistant/components/season/translations/sensor.fr.json index cf59614e8ac..cf2e8155dfe 100644 --- a/homeassistant/components/season/translations/sensor.fr.json +++ b/homeassistant/components/season/translations/sensor.fr.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "Automne", + "spring": "Printemps", + "summer": "\u00c9t\u00e9", + "winter": "Hiver" + }, "season__season__": { "autumn": "Automne", "spring": "Printemps", diff --git a/homeassistant/components/season/translations/sensor.nl.json b/homeassistant/components/season/translations/sensor.nl.json index 4c2511bbc7d..6391dc4d73d 100644 --- a/homeassistant/components/season/translations/sensor.nl.json +++ b/homeassistant/components/season/translations/sensor.nl.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "Herfst", + "spring": "Lente", + "summer": "Zomer", + "winter": "Winter" + }, "season__season__": { "autumn": "Herfst", "spring": "Lente", diff --git a/homeassistant/components/sense/translations/pt.json b/homeassistant/components/sense/translations/pt.json new file mode 100644 index 00000000000..b8a454fbaba --- /dev/null +++ b/homeassistant/components/sense/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/pt.json b/homeassistant/components/simplisafe/translations/pt.json index d5234ea3011..731ab4ad9a0 100644 --- a/homeassistant/components/simplisafe/translations/pt.json +++ b/homeassistant/components/simplisafe/translations/pt.json @@ -8,7 +8,7 @@ "user": { "data": { "password": "Palavra-passe", - "username": "Endere\u00e7o de e-mail" + "username": "Endere\u00e7o de correio eletr\u00f3nico" }, "title": "Preencha as suas informa\u00e7\u00f5es" } diff --git a/homeassistant/components/smartthings/translations/nl.json b/homeassistant/components/smartthings/translations/nl.json index 7e65645928e..b7e40798e83 100644 --- a/homeassistant/components/smartthings/translations/nl.json +++ b/homeassistant/components/smartthings/translations/nl.json @@ -8,6 +8,12 @@ "webhook_error": "SmartThings kon het in 'base_url` geconfigureerde endpoint niet goedkeuren. Lees de componentvereisten door." }, "step": { + "select_location": { + "data": { + "location_id": "Locatie" + }, + "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" diff --git a/homeassistant/components/starline/translations/pt.json b/homeassistant/components/starline/translations/pt.json new file mode 100644 index 00000000000..4bd578b953f --- /dev/null +++ b/homeassistant/components/starline/translations/pt.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "error_auth_mfa": "C\u00f3digo incorreto", + "error_auth_user": "Nome de utilizador ou palavra passe incorretos" + }, + "step": { + "auth_app": { + "data": { + "app_secret": "Segredo" + } + }, + "auth_mfa": { + "data": { + "mfa_code": "C\u00f3digo SMS" + } + }, + "auth_user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + }, + "description": "Conta de email StarLine e palavra-passe", + "title": "Credenciais de utilizador" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index 8ed10616a5e..f593ac26182 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -4,9 +4,11 @@ "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat" }, "error": { + "connection": "Error de connexi\u00f3: comprova l'amfitri\u00f3, la contrasenya i l'SSL", "login": "Error d\u2019inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya", "missing_data": "Falten dades: torna-ho a provar m\u00e9s tard o prova una altra configuraci\u00f3 diferent", - "otp_failed": "L'autenticaci\u00f3 en dos passos ha fallat, torna-ho a provar amb un nou codi" + "otp_failed": "L'autenticaci\u00f3 en dos passos ha fallat, torna-ho a provar amb un nou codi", + "unknown": "Error desconegut: consulta els registres per a m\u00e9s detalls." }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index 41814a1e68d..dd85dd12950 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -20,6 +20,7 @@ }, "link": { "data": { + "api_version": "DSM version", "password": "Password", "port": "Port (Optional)", "ssl": "Use SSL/TLS to connect to your NAS", @@ -30,6 +31,7 @@ }, "user": { "data": { + "api_version": "DSM version", "host": "Host", "password": "Password", "port": "Port (Optional)", diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index 5d6f5e53c5d..bda3e337dda 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -5,6 +5,11 @@ }, "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "Code" + } + }, "link": { "data": { "api_version": "DSM-versie", diff --git a/homeassistant/components/synology_dsm/translations/pt.json b/homeassistant/components/synology_dsm/translations/pt.json index 2c651539321..446b2d6a134 100644 --- a/homeassistant/components/synology_dsm/translations/pt.json +++ b/homeassistant/components/synology_dsm/translations/pt.json @@ -1,9 +1,26 @@ { "config": { + "error": { + "login": "Erro de autentica\u00e7\u00e3o: verifique seu nome de utilizador e palavra-passe" + }, "step": { + "2sa": { + "data": { + "otp_code": "C\u00f3digo" + } + }, + "link": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + }, "user": { "data": { - "port": "Porta (opcional)" + "host": "Servidor", + "password": "Palavra-passe", + "port": "Porta (opcional)", + "username": "Nome de Utilizador" } } } diff --git a/homeassistant/components/tado/translations/pt.json b/homeassistant/components/tado/translations/pt.json new file mode 100644 index 00000000000..b4642359973 --- /dev/null +++ b/homeassistant/components/tado/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Nome de Utilizador" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/pt.json b/homeassistant/components/tesla/translations/pt.json new file mode 100644 index 00000000000..0df67a94182 --- /dev/null +++ b/homeassistant/components/tesla/translations/pt.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Palavra-passe", + "username": "Endere\u00e7o de e-mail" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/timer/translations/hu.json b/homeassistant/components/timer/translations/hu.json index da6809f49b1..0f7314e662e 100644 --- a/homeassistant/components/timer/translations/hu.json +++ b/homeassistant/components/timer/translations/hu.json @@ -1,9 +1,9 @@ { "state": { "_": { - "active": "akt\u00edv", - "idle": "t\u00e9tlen", - "paused": "sz\u00fcneteltetve" + "active": "Akt\u00edv", + "idle": "T\u00e9tlen", + "paused": "Sz\u00fcnetel" } } } \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/pt.json b/homeassistant/components/transmission/translations/pt.json index 0421228d0f0..6b238362fd6 100644 --- a/homeassistant/components/transmission/translations/pt.json +++ b/homeassistant/components/transmission/translations/pt.json @@ -7,9 +7,10 @@ "user": { "data": { "host": "Servidor", + "name": "Nome", "password": "Palavra-passe", "port": "Porta", - "username": "Utilizador" + "username": "Nome de Utilizador" } } } diff --git a/homeassistant/components/unifi/translations/pt.json b/homeassistant/components/unifi/translations/pt.json index 3c1bfdbd8be..123385313e9 100644 --- a/homeassistant/components/unifi/translations/pt.json +++ b/homeassistant/components/unifi/translations/pt.json @@ -24,13 +24,17 @@ }, "options": { "step": { + "client_control": { + "title": "Op\u00e7\u00f5es UniFi 2/3" + }, "device_tracker": { "data": { "detection_time": "Tempo em segundos desde a \u00faltima vez que foi visto at\u00e9 ser considerado afastado", "track_clients": "Acompanhar clientes da rede", "track_devices": "Acompanhar dispositivos de rede (dispositivos Ubiquiti)", "track_wired_clients": "Incluir clientes da rede cablada" - } + }, + "title": "Op\u00e7\u00f5es UniFi 1/3" }, "init": { "data": { @@ -41,7 +45,8 @@ "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Criar sensores de uso de largura de banda para clientes da rede" - } + }, + "title": "Op\u00e7\u00f5es UniFi 3/3" } } } diff --git a/homeassistant/components/upnp/translations/en.json b/homeassistant/components/upnp/translations/en.json index a23a0fc7466..6da89c0e3d6 100644 --- a/homeassistant/components/upnp/translations/en.json +++ b/homeassistant/components/upnp/translations/en.json @@ -22,7 +22,7 @@ "enable_sensors": "Add traffic sensors", "igd": "UPnP/IGD" }, - "title": "Configuration options for the UPnP/IGD" + "title": "Configuration options" } } } diff --git a/homeassistant/components/vacuum/translations/hu.json b/homeassistant/components/vacuum/translations/hu.json index ef7a096468a..58d57124db0 100644 --- a/homeassistant/components/vacuum/translations/hu.json +++ b/homeassistant/components/vacuum/translations/hu.json @@ -21,7 +21,7 @@ "idle": "T\u00e9tlen", "off": "Ki", "on": "Be", - "paused": "Sz\u00fcneteltetve", + "paused": "Sz\u00fcnetel", "returning": "Dokkol\u00e1s folyamatban" } }, diff --git a/homeassistant/components/vesync/translations/pt.json b/homeassistant/components/vesync/translations/pt.json index 395907056e9..4df94dd3231 100644 --- a/homeassistant/components/vesync/translations/pt.json +++ b/homeassistant/components/vesync/translations/pt.json @@ -7,7 +7,7 @@ "user": { "data": { "password": "Palavra-passe", - "username": "Endere\u00e7o de e-mail" + "username": "Endere\u00e7o de email" }, "title": "Introduza o nome de utilizador e a palavra-passe" } diff --git a/homeassistant/components/vizio/translations/pt.json b/homeassistant/components/vizio/translations/pt.json new file mode 100644 index 00000000000..286cd58dd89 --- /dev/null +++ b/homeassistant/components/vizio/translations/pt.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nome" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/hu.json b/homeassistant/components/wled/translations/hu.json index 45f939cb855..5ffd902214e 100644 --- a/homeassistant/components/wled/translations/hu.json +++ b/homeassistant/components/wled/translations/hu.json @@ -1,24 +1,24 @@ { "config": { "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "already_configured": "Ez a WLED eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", + "connection_error": "Nem siker\u00fclt csatlakozni a WLED eszk\u00f6zh\u00f6z." }, "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "connection_error": "Nem siker\u00fclt csatlakozni a WLED eszk\u00f6zh\u00f6z." }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "Hosztn\u00e9v vagy IP c\u00edm" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "\u00c1ll\u00edtsd be a WLED-et a Home Assistant-ba val\u00f3 integr\u00e1l\u00e1shoz.", + "title": "Csatlakoztasd a WLED-et" }, "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Szeretn\u00e9d hozz\u00e1adni a(z) `{name}` WLED-et a Home Assistant-hoz?", + "title": "Felfedezett WLED eszk\u00f6z" } } } diff --git a/homeassistant/components/zha/translations/pt.json b/homeassistant/components/zha/translations/pt.json index 42221f7ae83..2c810af8eae 100644 --- a/homeassistant/components/zha/translations/pt.json +++ b/homeassistant/components/zha/translations/pt.json @@ -21,6 +21,16 @@ "warn": "Avisar" }, "trigger_subtype": { + "both_buttons": "Ambos os bot\u00f5es", + "button_1": "Primeiro bot\u00e3o", + "button_2": "Segundo bot\u00e3o", + "button_3": "Terceiro bot\u00e3o", + "button_4": "Quarto bot\u00e3o", + "button_5": "Quinto bot\u00e3o", + "button_6": "Sexto bot\u00e3o", + "close": "Fechar", + "dim_down": "Escurecer", + "dim_up": "Clariar", "left": "Esquerda" } } diff --git a/homeassistant/components/zwave/translations/hu.json b/homeassistant/components/zwave/translations/hu.json index 3d5641b45ac..72026949c78 100644 --- a/homeassistant/components/zwave/translations/hu.json +++ b/homeassistant/components/zwave/translations/hu.json @@ -26,8 +26,8 @@ "sleeping": "Alv\u00e1s" }, "query_stage": { - "dead": "Halott ({query_stage})", - "initializing": "Inicializ\u00e1l\u00e1s ({query_stage})" + "dead": "Halott", + "initializing": "Inicializ\u00e1l\u00e1s" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/pl.json b/homeassistant/components/zwave/translations/pl.json index 8728a3003ed..dd7ae5aead9 100644 --- a/homeassistant/components/zwave/translations/pl.json +++ b/homeassistant/components/zwave/translations/pl.json @@ -26,8 +26,8 @@ "sleeping": "u\u015bpiony" }, "query_stage": { - "dead": "martwy ({query_stage})", - "initializing": "inicjalizacja ({query_stage})" + "dead": "martwy", + "initializing": "inicjalizacja" } } } \ No newline at end of file From 561b033ebb61b5c5498432c2597c86854b427385 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 23 Apr 2020 09:44:28 +0300 Subject: [PATCH 010/511] Add coverage to qwikswitch (#33939) --- .coveragerc | 1 - .../components/qwikswitch/__init__.py | 17 +- homeassistant/components/qwikswitch/sensor.py | 6 +- tests/components/qwikswitch/test_init.py | 386 ++++++++++++++++-- 4 files changed, 362 insertions(+), 48 deletions(-) diff --git a/.coveragerc b/.coveragerc index 9182edfd756..bec37425416 100644 --- a/.coveragerc +++ b/.coveragerc @@ -570,7 +570,6 @@ omit = homeassistant/components/qrcode/image_processing.py homeassistant/components/quantum_gateway/device_tracker.py homeassistant/components/qvr_pro/* - homeassistant/components/qwikswitch/* homeassistant/components/rachio/* homeassistant/components/radarr/sensor.py homeassistant/components/radiotherm/climate.py diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py index ffe87797358..850429c1752 100644 --- a/homeassistant/components/qwikswitch/__init__.py +++ b/homeassistant/components/qwikswitch/__init__.py @@ -75,7 +75,7 @@ class QSEntity(Entity): return self._name @property - def poll(self): + def should_poll(self): """QS sensors gets packets in update_packet.""" return False @@ -165,9 +165,9 @@ async def async_setup(hass, config): comps = {"switch": [], "light": [], "sensor": [], "binary_sensor": []} - try: - sensor_ids = [] - for sens in sensors: + sensor_ids = [] + for sens in sensors: + try: _, _type = SENSORS[sens["type"]] sensor_ids.append(sens["id"]) if _type is bool: @@ -179,9 +179,12 @@ async def async_setup(hass, config): _LOGGER.warning( "%s should only be used for binary_sensors: %s", _key, sens ) - - except KeyError: - _LOGGER.warning("Sensor validation failed") + except KeyError: + _LOGGER.warning( + "Sensor validation failed for sensor id=%s type=%s", + sens["id"], + sens["type"], + ) for qsid, dev in qsusb.devices.items(): if qsid in switches: diff --git a/homeassistant/components/qwikswitch/sensor.py b/homeassistant/components/qwikswitch/sensor.py index 9609af42f65..53cf68ccdba 100644 --- a/homeassistant/components/qwikswitch/sensor.py +++ b/homeassistant/components/qwikswitch/sensor.py @@ -34,8 +34,10 @@ class QSSensor(QSEntity): sensor_type = sensor["type"] self._decode, self.unit = SENSORS[sensor_type] - if isinstance(self.unit, type): - self.unit = f"{sensor_type}:{self.channel}" + # this cannot happen because it only happens in bool and this should be redirected to binary_sensor + assert not isinstance( + self.unit, type + ), f"boolean sensor id={sensor['id']} name={sensor['name']}" @callback def update_packet(self, packet): diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py index 93e8e3fe8df..b0be2dbc81c 100644 --- a/tests/components/qwikswitch/test_init.py +++ b/tests/components/qwikswitch/test_init.py @@ -2,70 +2,83 @@ import asyncio import logging +from aiohttp.client_exceptions import ClientError +from asynctest import Mock +import pytest +from yarl import URL + from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH -from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component -from tests.test_util.aiohttp import MockLongPollSideEffect +from tests.test_util.aiohttp import AiohttpClientMockResponse, MockLongPollSideEffect _LOGGER = logging.getLogger(__name__) -DEVICES = [ - { - "id": "@000001", - "name": "Switch 1", - "type": "rel", - "val": "OFF", - "time": "1522777506", - "rssi": "51%", - }, - { - "id": "@000002", - "name": "Light 2", - "type": "rel", - "val": "ON", - "time": "1522777507", - "rssi": "45%", - }, - { - "id": "@000003", - "name": "Dim 3", - "type": "dim", - "val": "280c00", - "time": "1522777544", - "rssi": "62%", - }, -] +@pytest.fixture +def qs_devices(): + """Return a set of devices as a response.""" + return [ + { + "id": "@a00001", + "name": "Switch 1", + "type": "rel", + "val": "OFF", + "time": "1522777506", + "rssi": "51%", + }, + { + "id": "@a00002", + "name": "Light 2", + "type": "rel", + "val": "ON", + "time": "1522777507", + "rssi": "45%", + }, + { + "id": "@a00003", + "name": "Dim 3", + "type": "dim", + "val": "280c00", + "time": "1522777544", + "rssi": "62%", + }, + ] -async def test_binary_sensor_device(hass, aioclient_mock): +EMPTY_PACKET = {"cmd": ""} + + +async def test_binary_sensor_device(hass, aioclient_mock, qs_devices): """Test a binary sensor device.""" config = { "qwikswitch": { "sensors": {"name": "s1", "id": "@a00001", "channel": 1, "type": "imod"} } } - aioclient_mock.get("http://127.0.0.1:2020/&device", json=DEVICES) + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) listen_mock = MockLongPollSideEffect() aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) - await async_setup_component(hass, QWIKSWITCH, config) - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() await hass.async_block_till_done() + # verify initial state is off per the 'val' in qs_devices state_obj = hass.states.get("binary_sensor.s1") assert state_obj.state == "off" + # receive turn on command from network listen_mock.queue_response( - json={"id": "@a00001", "cmd": "", "data": "4e0e1601", "rssi": "61%"} + json={"id": "@a00001", "cmd": "STATUS.ACK", "data": "4e0e1601", "rssi": "61%"} ) await asyncio.sleep(0.01) await hass.async_block_till_done() state_obj = hass.states.get("binary_sensor.s1") assert state_obj.state == "on" + # receive turn off command from network listen_mock.queue_response( - json={"id": "@a00001", "cmd": "", "data": "4e0e1701", "rssi": "61%"}, + json={"id": "@a00001", "cmd": "STATUS.ACK", "data": "4e0e1701", "rssi": "61%"}, ) await asyncio.sleep(0.01) await hass.async_block_till_done() @@ -75,7 +88,7 @@ async def test_binary_sensor_device(hass, aioclient_mock): listen_mock.stop() -async def test_sensor_device(hass, aioclient_mock): +async def test_sensor_device(hass, aioclient_mock, qs_devices): """Test a sensor device.""" config = { "qwikswitch": { @@ -87,16 +100,17 @@ async def test_sensor_device(hass, aioclient_mock): } } } - aioclient_mock.get("http://127.0.0.1:2020/&device", json=DEVICES) + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) listen_mock = MockLongPollSideEffect() aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) - await async_setup_component(hass, QWIKSWITCH, config) - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() await hass.async_block_till_done() state_obj = hass.states.get("sensor.ss1") assert state_obj.state == "None" + # receive command that sets the sensor value listen_mock.queue_response( json={"id": "@a00001", "name": "ss1", "type": "rel", "val": "4733800001a00000"}, ) @@ -106,3 +120,299 @@ async def test_sensor_device(hass, aioclient_mock): assert state_obj.state == "416" listen_mock.stop() + + +async def test_switch_device(hass, aioclient_mock, qs_devices): + """Test a switch device.""" + + async def get_devices_json(method, url, data): + return AiohttpClientMockResponse(method=method, url=url, json=qs_devices) + + config = {"qwikswitch": {"switches": ["@a00001"]}} + aioclient_mock.get("http://127.0.0.1:2020/&device", side_effect=get_devices_json) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + + # verify initial state is off per the 'val' in qs_devices + state_obj = hass.states.get("switch.switch_1") + assert state_obj.state == "off" + + # ask hass to turn on and verify command is sent to device + aioclient_mock.mock_calls.clear() + aioclient_mock.get("http://127.0.0.1:2020/@a00001=100", json={"data": "OK"}) + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.switch_1"}, blocking=True + ) + await asyncio.sleep(0.01) + assert ( + "GET", + URL("http://127.0.0.1:2020/@a00001=100"), + None, + None, + ) in aioclient_mock.mock_calls + # verify state is on + state_obj = hass.states.get("switch.switch_1") + assert state_obj.state == "on" + + # ask hass to turn off and verify command is sent to device + aioclient_mock.mock_calls.clear() + aioclient_mock.get("http://127.0.0.1:2020/@a00001=0", json={"data": "OK"}) + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.switch_1"}, blocking=True + ) + assert ( + "GET", + URL("http://127.0.0.1:2020/@a00001=0"), + None, + None, + ) in aioclient_mock.mock_calls + # verify state is off + state_obj = hass.states.get("switch.switch_1") + assert state_obj.state == "off" + + # check if setting the value in the network show in hass + qs_devices[0]["val"] = "ON" + listen_mock.queue_response(json=EMPTY_PACKET) + await hass.async_block_till_done() + state_obj = hass.states.get("switch.switch_1") + assert state_obj.state == "on" + + listen_mock.stop() + + +async def test_light_device(hass, aioclient_mock, qs_devices): + """Test a light device.""" + + async def get_devices_json(method, url, data): + return AiohttpClientMockResponse(method=method, url=url, json=qs_devices) + + config = {"qwikswitch": {}} + aioclient_mock.get("http://127.0.0.1:2020/&device", side_effect=get_devices_json) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + + # verify initial state is on per the 'val' in qs_devices + state_obj = hass.states.get("light.dim_3") + assert state_obj.state == "on" + assert state_obj.attributes["brightness"] == 255 + + # ask hass to turn off and verify command is sent to device + aioclient_mock.mock_calls.clear() + aioclient_mock.get("http://127.0.0.1:2020/@a00003=0", json={"data": "OK"}) + await hass.services.async_call( + "light", "turn_off", {"entity_id": "light.dim_3"}, blocking=True + ) + await asyncio.sleep(0.01) + assert ( + "GET", + URL("http://127.0.0.1:2020/@a00003=0"), + None, + None, + ) in aioclient_mock.mock_calls + state_obj = hass.states.get("light.dim_3") + assert state_obj.state == "off" + + # change brightness in network and check that hass updates + qs_devices[2]["val"] = "280c55" # half dimmed + listen_mock.queue_response(json=EMPTY_PACKET) + await asyncio.sleep(0.01) + await hass.async_block_till_done() + state_obj = hass.states.get("light.dim_3") + assert state_obj.state == "on" + assert 16 < state_obj.attributes["brightness"] < 240 + + # turn off in the network and see that it is off in hass as well + qs_devices[2]["val"] = "280c78" # off + listen_mock.queue_response(json=EMPTY_PACKET) + await asyncio.sleep(0.01) + await hass.async_block_till_done() + state_obj = hass.states.get("light.dim_3") + assert state_obj.state == "off" + + # ask hass to turn on and verify command is sent to device + aioclient_mock.mock_calls.clear() + aioclient_mock.get("http://127.0.0.1:2020/@a00003=100", json={"data": "OK"}) + await hass.services.async_call( + "light", "turn_on", {"entity_id": "light.dim_3"}, blocking=True + ) + assert ( + "GET", + URL("http://127.0.0.1:2020/@a00003=100"), + None, + None, + ) in aioclient_mock.mock_calls + await hass.async_block_till_done() + state_obj = hass.states.get("light.dim_3") + assert state_obj.state == "on" + + listen_mock.stop() + + +async def test_button(hass, aioclient_mock, qs_devices): + """Test that buttons fire an event.""" + + async def get_devices_json(method, url, data): + return AiohttpClientMockResponse(method=method, url=url, json=qs_devices) + + config = {"qwikswitch": {"button_events": "TOGGLE"}} + aioclient_mock.get("http://127.0.0.1:2020/&device", side_effect=get_devices_json) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + + button_pressed = Mock() + hass.bus.async_listen_once("qwikswitch.button.@a00002", button_pressed) + listen_mock.queue_response(json={"id": "@a00002", "cmd": "TOGGLE"},) + await asyncio.sleep(0.01) + await hass.async_block_till_done() + button_pressed.assert_called_once() + + listen_mock.stop() + + +async def test_failed_update_devices(hass, aioclient_mock): + """Test that code behaves correctly when unable to get the devices.""" + + config = {"qwikswitch": {}} + aioclient_mock.get("http://127.0.0.1:2020/&device", exc=ClientError()) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert not await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + listen_mock.stop() + + +async def test_single_invalid_sensor(hass, aioclient_mock, qs_devices): + """Test that a single misconfigured sensor doesn't block the others.""" + + config = { + "qwikswitch": { + "sensors": [ + {"name": "ss1", "id": "@a00001", "channel": 1, "type": "qwikcord"}, + {"name": "ss2", "id": "@a00002", "channel": 1, "type": "ERROR_TYPE"}, + {"name": "ss3", "id": "@a00003", "channel": 1, "type": "qwikcord"}, + ] + } + } + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + await asyncio.sleep(0.01) + assert hass.states.get("sensor.ss1") + assert not hass.states.get("sensor.ss2") + assert hass.states.get("sensor.ss3") + listen_mock.stop() + + +async def test_non_binary_sensor_with_binary_args( + hass, aioclient_mock, qs_devices, caplog +): + """Test that the system logs a warning when a non-binary device has binary specific args.""" + + config = { + "qwikswitch": { + "sensors": [ + { + "name": "ss1", + "id": "@a00001", + "channel": 1, + "type": "qwikcord", + "invert": True, + }, + ] + } + } + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + await asyncio.sleep(0.01) + await hass.async_block_till_done() + assert hass.states.get("sensor.ss1") + assert "invert should only be used for binary_sensors" in caplog.text + listen_mock.stop() + + +async def test_non_relay_switch(hass, aioclient_mock, qs_devices, caplog): + """Test that the system logs a warning when a switch is configured for a device that is not a relay.""" + + config = {"qwikswitch": {"switches": ["@a00003"]}} + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + await asyncio.sleep(0.01) + await hass.async_block_till_done() + assert not hass.states.get("switch.dim_3") + assert "You specified a switch that is not a relay @a00003" in caplog.text + listen_mock.stop() + + +async def test_unknown_device(hass, aioclient_mock, qs_devices, caplog): + """Test that the system logs a warning when a network device has unknown type.""" + + config = {"qwikswitch": {}} + qs_devices[1]["type"] = "ERROR_TYPE" + aioclient_mock.get("http://127.0.0.1:2020/&device", json=qs_devices) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_start() + await hass.async_block_till_done() + await asyncio.sleep(0.01) + await hass.async_block_till_done() + assert hass.states.get("light.switch_1") + assert not hass.states.get("light.light_2") + assert hass.states.get("light.dim_3") + assert "Ignored unknown QSUSB device" in caplog.text + listen_mock.stop() + + +async def test_no_discover_info(hass, hass_storage, aioclient_mock, caplog): + """Test that discovery with no discovery_info does not result in errors.""" + config = { + "qwikswitch": {}, + "light": {"platform": "qwikswitch"}, + "switch": {"platform": "qwikswitch"}, + "sensor": {"platform": "qwikswitch"}, + "binary_sensor": {"platform": "qwikswitch"}, + } + aioclient_mock.get( + "http://127.0.0.1:2020/&device", + json=[ + { + "id": "@a00001", + "name": "Switch 1", + "type": "ERROR_TYPE", + "val": "OFF", + "time": "1522777506", + "rssi": "51%", + }, + ], + ) + listen_mock = MockLongPollSideEffect() + aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock) + assert await async_setup_component(hass, "light", config) + assert await async_setup_component(hass, "switch", config) + assert await async_setup_component(hass, "sensor", config) + assert await async_setup_component(hass, "binary_sensor", config) + await hass.async_start() + await hass.async_block_till_done() + assert "Error while setting up qwikswitch platform" not in caplog.text + listen_mock.stop() From 116f7934d31bcf5ecf699ae6c42c68f7f84e7cb9 Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Thu, 23 Apr 2020 06:50:55 -0400 Subject: [PATCH 011/511] Fix deleting and readding nws entry (#34555) * fix deleting and readding nws * Clean up * Fix variable name clash Co-authored-by: Martin Hjelmare --- homeassistant/components/nws/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index fa0c7554a01..f6cdc7c57cd 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -53,7 +53,6 @@ def base_unique_id(latitude, longitude): async def async_setup(hass: HomeAssistant, config: dict): """Set up the National Weather Service (NWS) component.""" - hass.data.setdefault(DOMAIN, {}) return True @@ -93,8 +92,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): update_method=nws_data.update_forecast_hourly, update_interval=DEFAULT_SCAN_INTERVAL, ) - - hass.data[DOMAIN][entry.entry_id] = { + nws_hass_data = hass.data.setdefault(DOMAIN, {}) + nws_hass_data[entry.entry_id] = { NWS_DATA: nws_data, COORDINATOR_OBSERVATION: coordinator_observation, COORDINATOR_FORECAST: coordinator_forecast, From a0fbf9ba47611334df91a8c721e90f9ae0b38e2b Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 23 Apr 2020 13:14:48 +0200 Subject: [PATCH 012/511] Remove hap pyc file (#34563) --- .../__pycache__/hap.cpython-37.pyc.139745820048096 | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 diff --git a/homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 b/homeassistant/components/homematicip_cloud/__pycache__/hap.cpython-37.pyc.139745820048096 deleted file mode 100644 index e69de29bb2d..00000000000 From c3689d741622d669deb161d5c242d7c495813c66 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 23 Apr 2020 16:48:24 +0200 Subject: [PATCH 013/511] UniFi - Store controller in config_entry.entry_id (#34553) * Store controller in config_entry.entry_id * Clean up imports --- homeassistant/components/unifi/__init__.py | 30 ++++--- homeassistant/components/unifi/config_flow.py | 12 +-- homeassistant/components/unifi/controller.py | 15 ++-- .../components/unifi/device_tracker.py | 7 +- homeassistant/components/unifi/sensor.py | 4 +- homeassistant/components/unifi/switch.py | 4 +- .../components/unifi/unifi_client.py | 3 +- tests/components/unifi/test_config_flow.py | 17 ++-- tests/components/unifi/test_controller.py | 83 ++++++++++--------- tests/components/unifi/test_device_tracker.py | 62 +++++++------- tests/components/unifi/test_init.py | 23 ++--- tests/components/unifi/test_sensor.py | 44 +++++----- tests/components/unifi/test_switch.py | 68 +++++++-------- 13 files changed, 190 insertions(+), 182 deletions(-) diff --git a/homeassistant/components/unifi/__init__.py b/homeassistant/components/unifi/__init__.py index e3225a2d210..610d39b0fb9 100644 --- a/homeassistant/components/unifi/__init__.py +++ b/homeassistant/components/unifi/__init__.py @@ -7,7 +7,12 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from .config_flow import get_controller_id_from_config_entry -from .const import ATTR_MANUFACTURER, DOMAIN, LOGGER, UNIFI_WIRELESS_CLIENTS +from .const import ( + ATTR_MANUFACTURER, + DOMAIN as UNIFI_DOMAIN, + LOGGER, + UNIFI_WIRELESS_CLIENTS, +) from .controller import UniFiController SAVE_DELAY = 10 @@ -15,7 +20,8 @@ STORAGE_KEY = "unifi_data" STORAGE_VERSION = 1 CONFIG_SCHEMA = vol.Schema( - cv.deprecated(DOMAIN, invalidation_version="0.109"), {DOMAIN: cv.match_all} + cv.deprecated(UNIFI_DOMAIN, invalidation_version="0.109"), + {UNIFI_DOMAIN: cv.match_all}, ) @@ -29,16 +35,13 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up the UniFi component.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} + hass.data.setdefault(UNIFI_DOMAIN, {}) controller = UniFiController(hass, config_entry) - if not await controller.async_setup(): return False - controller_id = get_controller_id_from_config_entry(config_entry) - hass.data[DOMAIN][controller_id] = controller + hass.data[UNIFI_DOMAIN][config_entry.entry_id] = controller hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, controller.shutdown) @@ -62,8 +65,7 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - controller_id = get_controller_id_from_config_entry(config_entry) - controller = hass.data[DOMAIN].pop(controller_id) + controller = hass.data[UNIFI_DOMAIN].pop(config_entry.entry_id) return await controller.async_reset() @@ -90,15 +92,21 @@ class UnifiWirelessClients: def get_data(self, config_entry): """Get data related to a specific controller.""" controller_id = get_controller_id_from_config_entry(config_entry) - data = self.data.get(controller_id, {"wireless_devices": []}) + key = config_entry.entry_id + if controller_id in self.data: + key = controller_id + + data = self.data.get(key, {"wireless_devices": []}) return set(data["wireless_devices"]) @callback def update_data(self, data, config_entry): """Update data and schedule to save to file.""" controller_id = get_controller_id_from_config_entry(config_entry) - self.data[controller_id] = {"wireless_devices": list(data)} + if controller_id in self.data: + self.data.pop(controller_id) + self.data[config_entry.entry_id] = {"wireless_devices": list(data)} self._store.async_delay_save(self._data_to_save, SAVE_DELAY) @callback diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index e91001e51be..f42acf54e9d 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -28,7 +28,7 @@ from .const import ( CONF_TRACK_WIRED_CLIENTS, CONTROLLER_ID, DEFAULT_POE_CLIENTS, - DOMAIN, + DOMAIN as UNIFI_DOMAIN, LOGGER, ) from .controller import get_controller @@ -48,13 +48,7 @@ def get_controller_id_from_config_entry(config_entry): ) -@callback -def get_controller_from_config_entry(hass, config_entry): - """Return controller with a matching bridge id.""" - return hass.data[DOMAIN][get_controller_id_from_config_entry(config_entry)] - - -class UnifiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): """Handle a UniFi config flow.""" VERSION = 1 @@ -179,7 +173,7 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Manage the UniFi options.""" - self.controller = get_controller_from_config_entry(self.hass, self.config_entry) + self.controller = self.hass.data[UNIFI_DOMAIN][self.config_entry.entry_id] self.options[CONF_BLOCK_CLIENT] = self.controller.option_block_clients return await self.async_step_device_tracker() diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 96f1194df24..807e727777c 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -17,7 +17,7 @@ from aiounifi.events import WIRELESS_CLIENT_CONNECTED, WIRELESS_GUEST_CONNECTED from aiounifi.websocket import STATE_DISCONNECTED, STATE_RUNNING import async_timeout -from homeassistant.components.device_tracker import DOMAIN as DT_DOMAIN +from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import CONF_HOST @@ -46,14 +46,14 @@ from .const import ( DEFAULT_TRACK_CLIENTS, DEFAULT_TRACK_DEVICES, DEFAULT_TRACK_WIRED_CLIENTS, - DOMAIN, + DOMAIN as UNIFI_DOMAIN, LOGGER, UNIFI_WIRELESS_CLIENTS, ) from .errors import AuthenticationRequired, CannotConnect RETRY_TIMER = 15 -SUPPORTED_PLATFORMS = [DT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] +SUPPORTED_PLATFORMS = [TRACKER_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] class UniFiController: @@ -283,14 +283,9 @@ class UniFiController: return True @staticmethod - async def async_config_entry_updated(hass, entry) -> None: + async def async_config_entry_updated(hass, config_entry) -> None: """Handle signals of config entry being updated.""" - controller_id = CONTROLLER_ID.format( - host=entry.data[CONF_CONTROLLER][CONF_HOST], - site=entry.data[CONF_CONTROLLER][CONF_SITE_ID], - ) - controller = hass.data[DOMAIN][controller_id] - + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] async_dispatcher_send(hass, controller.signal_options_update) @callback diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 2ac516fac55..4b72c520b31 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -4,16 +4,15 @@ import logging from homeassistant.components.device_tracker import DOMAIN from homeassistant.components.device_tracker.config_entry import ScannerEntity from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER -from homeassistant.components.unifi.config_flow import get_controller_from_config_entry -from homeassistant.components.unifi.unifi_entity_base import UniFiBase from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_point_in_utc_time import homeassistant.util.dt as dt_util -from .const import ATTR_MANUFACTURER +from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN from .unifi_client import UniFiClient +from .unifi_entity_base import UniFiBase LOGGER = logging.getLogger(__name__) @@ -45,7 +44,7 @@ DEVICE_TRACKER = "device" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up device tracker for UniFi component.""" - controller = get_controller_from_config_entry(hass, config_entry) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.entities[DOMAIN] = {CLIENT_TRACKER: set(), DEVICE_TRACKER: set()} # Restore clients that is not a part of active clients list. diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 964db5820b8..9077db49dac 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -2,11 +2,11 @@ import logging from homeassistant.components.sensor import DOMAIN -from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from .const import DOMAIN as UNIFI_DOMAIN from .unifi_client import UniFiClient LOGGER = logging.getLogger(__name__) @@ -21,7 +21,7 @@ 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 sensors for UniFi integration.""" - controller = get_controller_from_config_entry(hass, config_entry) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.entities[DOMAIN] = {RX_SENSOR: set(), TX_SENSOR: set()} @callback diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 5fb6daf524c..73e1dd131bc 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -2,11 +2,11 @@ import logging from homeassistant.components.switch import DOMAIN, SwitchDevice -from homeassistant.components.unifi.config_flow import get_controller_from_config_entry from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity +from .const import DOMAIN as UNIFI_DOMAIN from .unifi_client import UniFiClient LOGGER = logging.getLogger(__name__) @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): Switches are controlling network access and switch ports with POE. """ - controller = get_controller_from_config_entry(hass, config_entry) + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.entities[DOMAIN] = {BLOCK_SWITCH: set(), POE_SWITCH: set()} if controller.site_role != "admin": diff --git a/homeassistant/components/unifi/unifi_client.py b/homeassistant/components/unifi/unifi_client.py index 8f91d4e1de3..c9bd038dd77 100644 --- a/homeassistant/components/unifi/unifi_client.py +++ b/homeassistant/components/unifi/unifi_client.py @@ -15,10 +15,11 @@ from aiounifi.events import ( WIRELESS_CLIENT_UNBLOCKED, ) -from homeassistant.components.unifi.unifi_entity_base import UniFiBase from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from .unifi_entity_base import UniFiBase + LOGGER = logging.getLogger(__name__) CLIENT_BLOCKED = (WIRED_CLIENT_BLOCKED, WIRELESS_CLIENT_BLOCKED) diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index c6b4f27e7f4..bad191e1600 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -3,8 +3,6 @@ import aiounifi from asynctest import patch from homeassistant import data_entry_flow -from homeassistant.components import unifi -from homeassistant.components.unifi import config_flow from homeassistant.components.unifi.const import ( CONF_ALLOW_BANDWIDTH_SENSORS, CONF_BLOCK_CLIENT, @@ -17,6 +15,7 @@ from homeassistant.components.unifi.const import ( CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, + DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import ( CONF_HOST, @@ -39,7 +38,7 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): """Test config flow.""" mock_discovery.return_value = "1" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -97,7 +96,7 @@ async def test_flow_works(hass, aioclient_mock, mock_discovery): async def test_flow_works_multiple_sites(hass, aioclient_mock): """Test config flow works when finding multiple sites.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -143,12 +142,12 @@ async def test_flow_works_multiple_sites(hass, aioclient_mock): async def test_flow_fails_site_already_configured(hass, aioclient_mock): """Test config flow.""" entry = MockConfigEntry( - domain=unifi.DOMAIN, data={"controller": {"host": "1.2.3.4", "site": "site_id"}} + domain=UNIFI_DOMAIN, data={"controller": {"host": "1.2.3.4", "site": "site_id"}} ) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -188,7 +187,7 @@ async def test_flow_fails_site_already_configured(hass, aioclient_mock): async def test_flow_fails_user_credentials_faulty(hass, aioclient_mock): """Test config flow.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -215,7 +214,7 @@ async def test_flow_fails_user_credentials_faulty(hass, aioclient_mock): async def test_flow_fails_controller_unavailable(hass, aioclient_mock): """Test config flow.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -242,7 +241,7 @@ async def test_flow_fails_controller_unavailable(hass, aioclient_mock): async def test_flow_fails_unknown_problem(hass, aioclient_mock): """Test config flow.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + UNIFI_DOMAIN, context={"source": "user"} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 844eaa5d222..824ffbffc41 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -7,12 +7,25 @@ import aiounifi from asynctest import patch import pytest -from homeassistant.components import unifi +from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.unifi.const import ( CONF_CONTROLLER, CONF_SITE_ID, + DEFAULT_ALLOW_BANDWIDTH_SENSORS, + DEFAULT_DETECTION_TIME, + DEFAULT_TRACK_CLIENTS, + DEFAULT_TRACK_DEVICES, + DEFAULT_TRACK_WIRED_CLIENTS, + DOMAIN as UNIFI_DOMAIN, UNIFI_WIRELESS_CLIENTS, ) +from homeassistant.components.unifi.controller import ( + SUPPORTED_PLATFORMS, + get_controller, +) +from homeassistant.components.unifi.errors import AuthenticationRequired, CannotConnect from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -68,10 +81,10 @@ async def setup_unifi_integration( controllers=None, ): """Create the UniFi controller.""" - assert await async_setup_component(hass, unifi.DOMAIN, {}) + assert await async_setup_component(hass, UNIFI_DOMAIN, {}) config_entry = MockConfigEntry( - domain=unifi.DOMAIN, + domain=UNIFI_DOMAIN, data=deepcopy(config), options=deepcopy(options), entry_id=1, @@ -124,10 +137,9 @@ async def setup_unifi_integration( await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - controller_id = unifi.get_controller_id_from_config_entry(config_entry) - if controller_id not in hass.data[unifi.DOMAIN]: + if config_entry.entry_id not in hass.data[UNIFI_DOMAIN]: return None - controller = hass.data[unifi.DOMAIN][controller_id] + controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.mock_client_responses = mock_client_responses controller.mock_device_responses = mock_device_responses @@ -147,31 +159,22 @@ async def test_controller_setup(hass): controller = await setup_unifi_integration(hass) entry = controller.config_entry - assert len(forward_entry_setup.mock_calls) == len( - unifi.controller.SUPPORTED_PLATFORMS - ) - assert forward_entry_setup.mock_calls[0][1] == (entry, "device_tracker") - assert forward_entry_setup.mock_calls[1][1] == (entry, "sensor") - assert forward_entry_setup.mock_calls[2][1] == (entry, "switch") + assert len(forward_entry_setup.mock_calls) == len(SUPPORTED_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) assert controller.host == CONTROLLER_DATA[CONF_HOST] assert controller.site == CONTROLLER_DATA[CONF_SITE_ID] assert controller.site_name in SITES assert controller.site_role == SITES[controller.site_name]["role"] - assert ( - controller.option_allow_bandwidth_sensors - == unifi.const.DEFAULT_ALLOW_BANDWIDTH_SENSORS - ) + assert controller.option_allow_bandwidth_sensors == DEFAULT_ALLOW_BANDWIDTH_SENSORS assert isinstance(controller.option_block_clients, list) - assert controller.option_track_clients == unifi.const.DEFAULT_TRACK_CLIENTS - assert controller.option_track_devices == unifi.const.DEFAULT_TRACK_DEVICES - assert ( - controller.option_track_wired_clients == unifi.const.DEFAULT_TRACK_WIRED_CLIENTS - ) - assert controller.option_detection_time == timedelta( - seconds=unifi.const.DEFAULT_DETECTION_TIME - ) + assert controller.option_track_clients == DEFAULT_TRACK_CLIENTS + assert controller.option_track_devices == DEFAULT_TRACK_DEVICES + assert controller.option_track_wired_clients == DEFAULT_TRACK_WIRED_CLIENTS + assert controller.option_detection_time == timedelta(seconds=DEFAULT_DETECTION_TIME) assert isinstance(controller.option_ssid_filter, list) assert controller.mac is None @@ -184,23 +187,27 @@ async def test_controller_setup(hass): async def test_controller_mac(hass): """Test that it is possible to identify controller mac.""" controller = await setup_unifi_integration(hass, clients_response=[CONTROLLER_HOST]) - assert controller.mac == "10:00:00:00:00:01" + assert controller.mac == CONTROLLER_HOST["mac"] async def test_controller_not_accessible(hass): """Retry to login gets scheduled when connection fails.""" - with patch.object( - unifi.controller, "get_controller", side_effect=unifi.errors.CannotConnect + with patch( + "homeassistant.components.unifi.controller.get_controller", + side_effect=CannotConnect, ): await setup_unifi_integration(hass) - assert hass.data[unifi.DOMAIN] == {} + assert hass.data[UNIFI_DOMAIN] == {} async def test_controller_unknown_error(hass): """Unknown errors are handled.""" - with patch.object(unifi.controller, "get_controller", side_effect=Exception): + with patch( + "homeassistant.components.unifi.controller.get_controller", + side_effect=Exception, + ): await setup_unifi_integration(hass) - assert hass.data[unifi.DOMAIN] == {} + assert hass.data[UNIFI_DOMAIN] == {} async def test_reset_after_successful_setup(hass): @@ -245,7 +252,7 @@ async def test_get_controller(hass): with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( "aiounifi.Controller.login", return_value=True ): - assert await unifi.controller.get_controller(hass, **CONTROLLER_DATA) + assert await get_controller(hass, **CONTROLLER_DATA) async def test_get_controller_verify_ssl_false(hass): @@ -255,28 +262,28 @@ async def test_get_controller_verify_ssl_false(hass): with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( "aiounifi.Controller.login", return_value=True ): - assert await unifi.controller.get_controller(hass, **controller_data) + assert await get_controller(hass, **controller_data) async def test_get_controller_login_failed(hass): """Check that get_controller can handle a failed login.""" with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( "aiounifi.Controller.login", side_effect=aiounifi.Unauthorized - ), pytest.raises(unifi.errors.AuthenticationRequired): - await unifi.controller.get_controller(hass, **CONTROLLER_DATA) + ), pytest.raises(AuthenticationRequired): + await get_controller(hass, **CONTROLLER_DATA) async def test_get_controller_controller_unavailable(hass): """Check that get_controller can handle controller being unavailable.""" with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( "aiounifi.Controller.login", side_effect=aiounifi.RequestError - ), pytest.raises(unifi.errors.CannotConnect): - await unifi.controller.get_controller(hass, **CONTROLLER_DATA) + ), pytest.raises(CannotConnect): + await get_controller(hass, **CONTROLLER_DATA) async def test_get_controller_unknown_error(hass): """Check that get_controller can handle unknown errors.""" with patch("aiounifi.Controller.check_unifi_os", return_value=True), patch( "aiounifi.Controller.login", side_effect=aiounifi.AiounifiException - ), pytest.raises(unifi.errors.AuthenticationRequired): - await unifi.controller.get_controller(hass, **CONTROLLER_DATA) + ), pytest.raises(AuthenticationRequired): + await get_controller(hass, **CONTROLLER_DATA) diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index c204c75a122..df7c49a1bf7 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -7,8 +7,7 @@ from aiounifi.websocket import SIGNAL_DATA, STATE_DISCONNECTED, STATE_RUNNING from asynctest import patch from homeassistant import config_entries -from homeassistant.components import unifi -import homeassistant.components.device_tracker as device_tracker +from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.components.unifi.const import ( CONF_BLOCK_CLIENT, CONF_IGNORE_WIRED_BUG, @@ -16,6 +15,7 @@ from homeassistant.components.unifi.const import ( CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, CONF_TRACK_WIRED_CLIENTS, + DOMAIN as UNIFI_DOMAIN, ) from homeassistant.const import STATE_UNAVAILABLE from homeassistant.helpers import entity_registry @@ -99,18 +99,18 @@ async def test_platform_manually_configured(hass): """Test that nothing happens when configuring unifi through device tracker platform.""" assert ( await async_setup_component( - hass, device_tracker.DOMAIN, {device_tracker.DOMAIN: {"platform": "unifi"}} + hass, TRACKER_DOMAIN, {TRACKER_DOMAIN: {"platform": UNIFI_DOMAIN}} ) is False ) - assert unifi.DOMAIN not in hass.data + assert UNIFI_DOMAIN not in hass.data async def test_no_clients(hass): """Test the update_clients function when no clients are found.""" await setup_unifi_integration(hass) - assert len(hass.states.async_entity_ids("device_tracker")) == 0 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 0 async def test_tracked_devices(hass): @@ -125,7 +125,7 @@ async def test_tracked_devices(hass): devices_response=[DEVICE_1, DEVICE_2], known_wireless_clients=(CLIENT_4["mac"],), ) - assert len(hass.states.async_entity_ids("device_tracker")) == 5 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 5 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is not None @@ -186,7 +186,7 @@ async def test_remove_clients(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2] ) - assert len(hass.states.async_entity_ids("device_tracker")) == 2 + 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 @@ -201,7 +201,7 @@ async def test_remove_clients(hass): controller.api.session_handler(SIGNAL_DATA) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is None @@ -215,7 +215,7 @@ async def test_controller_state_change(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 2 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 # Controller unavailable controller.async_unifi_signalling_callback( @@ -245,7 +245,7 @@ async def test_option_track_clients(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 3 + 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 @@ -290,7 +290,7 @@ async def test_option_track_wired_clients(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 3 + 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 @@ -335,7 +335,7 @@ async def test_option_track_devices(hass): controller = await setup_unifi_integration( hass, clients_response=[CLIENT_1, CLIENT_2], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 3 + 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 @@ -378,7 +378,7 @@ async def test_option_track_devices(hass): async def test_option_ssid_filter(hass): """Test the SSID filter works.""" controller = await setup_unifi_integration(hass, clients_response=[CLIENT_3]) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 client_3 = hass.states.get("device_tracker.client_3") assert client_3 @@ -423,7 +423,7 @@ async def test_wireless_client_go_wired_issue(hass): client_1_client["last_seen"] = dt_util.as_timestamp(dt_util.utcnow()) controller = await setup_unifi_integration(hass, clients_response=[client_1_client]) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + 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 @@ -441,9 +441,7 @@ async def test_wireless_client_go_wired_issue(hass): assert client_1.attributes["is_wired"] is False with patch.object( - unifi.device_tracker.dt_util, - "utcnow", - return_value=(dt_util.utcnow() + timedelta(minutes=5)), + dt_util, "utcnow", return_value=(dt_util.utcnow() + timedelta(minutes=5)), ): event = {"meta": {"message": "sta:sync"}, "data": [client_1_client]} controller.api.message_handler(event) @@ -472,7 +470,7 @@ async def test_option_ignore_wired_bug(hass): controller = await setup_unifi_integration( hass, options={CONF_IGNORE_WIRED_BUG: True}, clients_response=[client_1_client] ) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + 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 @@ -504,7 +502,7 @@ async def test_restoring_client(hass): """Test the update_items function with some clients.""" config_entry = config_entries.ConfigEntry( version=1, - domain=unifi.DOMAIN, + domain=UNIFI_DOMAIN, title="Mock Title", data=ENTRY_CONFIG, source="test", @@ -516,16 +514,16 @@ async def test_restoring_client(hass): registry = await entity_registry.async_get_registry(hass) registry.async_get_or_create( - device_tracker.DOMAIN, - unifi.DOMAIN, - "{}-site_id".format(CLIENT_1["mac"]), + 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( - device_tracker.DOMAIN, - unifi.DOMAIN, - "{}-site_id".format(CLIENT_2["mac"]), + TRACKER_DOMAIN, + UNIFI_DOMAIN, + f'{CLIENT_2["mac"]}-site_id', suggested_object_id=CLIENT_2["hostname"], config_entry=config_entry, ) @@ -536,7 +534,7 @@ async def test_restoring_client(hass): clients_response=[CLIENT_2], clients_all_response=[CLIENT_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 2 + 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 @@ -546,11 +544,11 @@ async def test_dont_track_clients(hass): """Test don't track clients config works.""" await setup_unifi_integration( hass, - options={unifi.controller.CONF_TRACK_CLIENTS: False}, + options={CONF_TRACK_CLIENTS: False}, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 1 client_1 = hass.states.get("device_tracker.client_1") assert client_1 is None @@ -564,11 +562,11 @@ async def test_dont_track_devices(hass): """Test don't track devices config works.""" await setup_unifi_integration( hass, - options={unifi.controller.CONF_TRACK_DEVICES: False}, + options={CONF_TRACK_DEVICES: False}, clients_response=[CLIENT_1], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + 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 @@ -582,10 +580,10 @@ async def test_dont_track_wired_clients(hass): """Test don't track wired clients config works.""" await setup_unifi_integration( hass, - options={unifi.controller.CONF_TRACK_WIRED_CLIENTS: False}, + options={CONF_TRACK_WIRED_CLIENTS: False}, clients_response=[CLIENT_1, CLIENT_2], ) - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + 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 diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index 0ccc89cdb89..db6c1e30748 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -2,6 +2,7 @@ from unittest.mock import Mock, patch from homeassistant.components import unifi +from homeassistant.components.unifi.const import DOMAIN as UNIFI_DOMAIN from homeassistant.setup import async_setup_component from .test_controller import setup_unifi_integration @@ -11,29 +12,29 @@ from tests.common import MockConfigEntry, mock_coro async def test_setup_with_no_config(hass): """Test that we do not discover anything or try to set up a bridge.""" - assert await async_setup_component(hass, unifi.DOMAIN, {}) is True - assert unifi.DOMAIN not in hass.data + assert await async_setup_component(hass, UNIFI_DOMAIN, {}) is True + assert UNIFI_DOMAIN not in hass.data async def test_successful_config_entry(hass): """Test that configured options for a host are loaded via config entry.""" await setup_unifi_integration(hass) - assert hass.data[unifi.DOMAIN] + assert hass.data[UNIFI_DOMAIN] async def test_controller_fail_setup(hass): """Test that a failed setup still stores controller.""" - with patch.object(unifi, "UniFiController") as mock_cntrlr: - mock_cntrlr.return_value.async_setup.return_value = mock_coro(False) + with patch("homeassistant.components.unifi.UniFiController") as mock_controller: + mock_controller.return_value.async_setup.return_value = mock_coro(False) await setup_unifi_integration(hass) - assert hass.data[unifi.DOMAIN] == {} + assert hass.data[UNIFI_DOMAIN] == {} async def test_controller_no_mac(hass): """Test that configured options for a host are loaded via config entry.""" entry = MockConfigEntry( - domain=unifi.DOMAIN, + domain=UNIFI_DOMAIN, data={ "controller": { "host": "0.0.0.0", @@ -48,7 +49,9 @@ async def test_controller_no_mac(hass): ) entry.add_to_hass(hass) mock_registry = Mock() - with patch.object(unifi, "UniFiController") as mock_controller, patch( + with patch( + "homeassistant.components.unifi.UniFiController" + ) as mock_controller, patch( "homeassistant.helpers.device_registry.async_get_registry", return_value=mock_coro(mock_registry), ): @@ -64,7 +67,7 @@ async def test_controller_no_mac(hass): async def test_unload_entry(hass): """Test being able to unload an entry.""" controller = await setup_unifi_integration(hass) - assert hass.data[unifi.DOMAIN] + assert hass.data[UNIFI_DOMAIN] assert await unifi.async_unload_entry(hass, controller.config_entry) - assert not hass.data[unifi.DOMAIN] + assert not hass.data[UNIFI_DOMAIN] diff --git a/tests/components/unifi/test_sensor.py b/tests/components/unifi/test_sensor.py index 3c801474235..cc879a3d713 100644 --- a/tests/components/unifi/test_sensor.py +++ b/tests/components/unifi/test_sensor.py @@ -4,8 +4,14 @@ from copy import deepcopy from aiounifi.controller import MESSAGE_CLIENT_REMOVED from aiounifi.websocket import SIGNAL_DATA -from homeassistant.components import unifi -import homeassistant.components.sensor as sensor +from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.unifi.const import ( + CONF_ALLOW_BANDWIDTH_SENSORS, + CONF_TRACK_CLIENTS, + CONF_TRACK_DEVICES, + DOMAIN as UNIFI_DOMAIN, +) from homeassistant.setup import async_setup_component from .test_controller import setup_unifi_integration @@ -44,21 +50,21 @@ async def test_platform_manually_configured(hass): """Test that we do not discover anything or try to set up a controller.""" assert ( await async_setup_component( - hass, sensor.DOMAIN, {sensor.DOMAIN: {"platform": "unifi"}} + hass, SENSOR_DOMAIN, {SENSOR_DOMAIN: {"platform": UNIFI_DOMAIN}} ) is True ) - assert unifi.DOMAIN not in hass.data + assert UNIFI_DOMAIN not in hass.data async def test_no_clients(hass): """Test the update_clients function when no clients are found.""" controller = await setup_unifi_integration( - hass, options={unifi.const.CONF_ALLOW_BANDWIDTH_SENSORS: True}, + hass, options={CONF_ALLOW_BANDWIDTH_SENSORS: True}, ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("sensor")) == 0 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 0 async def test_sensors(hass): @@ -66,15 +72,15 @@ async def test_sensors(hass): controller = await setup_unifi_integration( hass, options={ - unifi.const.CONF_ALLOW_BANDWIDTH_SENSORS: True, - unifi.const.CONF_TRACK_CLIENTS: False, - unifi.const.CONF_TRACK_DEVICES: False, + CONF_ALLOW_BANDWIDTH_SENSORS: True, + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, }, clients_response=CLIENTS, ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("sensor")) == 4 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 wired_client_rx = hass.states.get("sensor.wired_client_name_rx") assert wired_client_rx.state == "1234.0" @@ -104,8 +110,7 @@ async def test_sensors(hass): assert wireless_client_tx.state == "6789.0" hass.config_entries.async_update_entry( - controller.config_entry, - options={unifi.const.CONF_ALLOW_BANDWIDTH_SENSORS: False}, + controller.config_entry, options={CONF_ALLOW_BANDWIDTH_SENSORS: False}, ) await hass.async_block_till_done() @@ -116,8 +121,7 @@ async def test_sensors(hass): assert wireless_client_tx is None hass.config_entries.async_update_entry( - controller.config_entry, - options={unifi.const.CONF_ALLOW_BANDWIDTH_SENSORS: True}, + controller.config_entry, options={CONF_ALLOW_BANDWIDTH_SENSORS: True}, ) await hass.async_block_till_done() @@ -131,12 +135,10 @@ async def test_sensors(hass): async def test_remove_sensors(hass): """Test the remove_items function with some clients.""" controller = await setup_unifi_integration( - hass, - options={unifi.const.CONF_ALLOW_BANDWIDTH_SENSORS: True}, - clients_response=CLIENTS, + hass, options={CONF_ALLOW_BANDWIDTH_SENSORS: True}, clients_response=CLIENTS, ) - assert len(hass.states.async_entity_ids("sensor")) == 4 - assert len(hass.states.async_entity_ids("device_tracker")) == 2 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 4 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 2 wired_client_rx = hass.states.get("sensor.wired_client_name_rx") assert wired_client_rx is not None @@ -155,8 +157,8 @@ async def test_remove_sensors(hass): controller.api.session_handler(SIGNAL_DATA) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("sensor")) == 2 - assert len(hass.states.async_entity_ids("device_tracker")) == 1 + assert len(hass.states.async_entity_ids(SENSOR_DOMAIN)) == 2 + 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 diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 5ea76472739..4dfd3bc4c9d 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -5,13 +5,15 @@ from aiounifi.controller import MESSAGE_CLIENT_REMOVED from aiounifi.websocket import SIGNAL_DATA from homeassistant import config_entries -from homeassistant.components import unifi -import homeassistant.components.switch as switch +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 ( CONF_BLOCK_CLIENT, CONF_TRACK_CLIENTS, CONF_TRACK_DEVICES, + DOMAIN as UNIFI_DOMAIN, ) +from homeassistant.components.unifi.switch import POE_SWITCH from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component @@ -200,11 +202,11 @@ async def test_platform_manually_configured(hass): """Test that we do not discover anything or try to set up a controller.""" assert ( await async_setup_component( - hass, switch.DOMAIN, {switch.DOMAIN: {"platform": "unifi"}} + hass, SWITCH_DOMAIN, {SWITCH_DOMAIN: {"platform": UNIFI_DOMAIN}} ) is True ) - assert unifi.DOMAIN not in hass.data + assert UNIFI_DOMAIN not in hass.data async def test_no_clients(hass): @@ -214,7 +216,7 @@ async def test_no_clients(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 async def test_controller_not_client(hass): @@ -227,7 +229,7 @@ async def test_controller_not_client(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 cloudkey = hass.states.get("switch.cloud_key") assert cloudkey is None @@ -245,7 +247,7 @@ async def test_not_admin(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 async def test_switches(hass): @@ -263,13 +265,13 @@ async def test_switches(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 3 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 3 switch_1 = hass.states.get("switch.poe_client_1") assert switch_1 is not None assert switch_1.state == "on" assert switch_1.attributes["power"] == "2.56" - assert switch_1.attributes["switch"] == "00:00:00:00:01:01" + assert switch_1.attributes[SWITCH_DOMAIN] == "00:00:00:00:01:01" assert switch_1.attributes["port"] == 1 assert switch_1.attributes["poe_mode"] == "auto" @@ -285,7 +287,7 @@ async def test_switches(hass): assert unblocked.state == "on" await hass.services.async_call( - "switch", "turn_off", {"entity_id": "switch.block_client_1"}, blocking=True + SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.block_client_1"}, blocking=True ) assert len(controller.mock_requests) == 5 assert controller.mock_requests[4] == { @@ -295,7 +297,7 @@ async def test_switches(hass): } await hass.services.async_call( - "switch", "turn_on", {"entity_id": "switch.block_client_1"}, blocking=True + SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.block_client_1"}, blocking=True ) assert len(controller.mock_requests) == 6 assert controller.mock_requests[5] == { @@ -313,7 +315,7 @@ async def test_remove_switches(hass): clients_response=[CLIENT_1, UNBLOCKED], devices_response=[DEVICE_1], ) - assert len(hass.states.async_entity_ids("switch")) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 poe_switch = hass.states.get("switch.poe_client_1") assert poe_switch is not None @@ -328,7 +330,7 @@ async def test_remove_switches(hass): controller.api.session_handler(SIGNAL_DATA) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 poe_switch = hass.states.get("switch.poe_client_1") assert poe_switch is None @@ -349,7 +351,7 @@ async def test_new_client_discovered_on_block_control(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 blocked = hass.states.get("switch.block_client_1") assert blocked is None @@ -361,7 +363,7 @@ async def test_new_client_discovered_on_block_control(hass): controller.api.session_handler("data") await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 blocked = hass.states.get("switch.block_client_1") assert blocked is not None @@ -373,7 +375,7 @@ async def test_option_block_clients(hass): options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, clients_all_response=[BLOCKED, UNBLOCKED], ) - assert len(hass.states.async_entity_ids("switch")) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 # Add a second switch hass.config_entries.async_update_entry( @@ -381,28 +383,28 @@ async def test_option_block_clients(hass): options={CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 # Remove the second switch again hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: [BLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 # Enable one and remove another one hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 # Remove one hass.config_entries.async_update_entry( controller.config_entry, options={CONF_BLOCK_CLIENT: []}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("switch")) == 0 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 async def test_new_client_discovered_on_poe_control(hass): @@ -415,7 +417,7 @@ async def test_new_client_discovered_on_poe_control(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 controller.api.websocket._data = { "meta": {"message": "sta:sync"}, @@ -425,10 +427,10 @@ async def test_new_client_discovered_on_poe_control(hass): # Calling a service will trigger the updates to run await hass.services.async_call( - "switch", "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True + SWITCH_DOMAIN, "turn_off", {"entity_id": "switch.poe_client_1"}, blocking=True ) assert len(controller.mock_requests) == 5 - assert len(hass.states.async_entity_ids("switch")) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 assert controller.mock_requests[4] == { "json": { "port_overrides": [{"port_idx": 1, "portconf_id": "1a1", "poe_mode": "off"}] @@ -438,7 +440,7 @@ async def test_new_client_discovered_on_poe_control(hass): } await hass.services.async_call( - "switch", "turn_on", {"entity_id": "switch.poe_client_1"}, blocking=True + SWITCH_DOMAIN, "turn_on", {"entity_id": "switch.poe_client_1"}, blocking=True ) assert len(controller.mock_requests) == 6 assert controller.mock_requests[4] == { @@ -467,7 +469,7 @@ async def test_ignore_multiple_poe_clients_on_same_port(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("device_tracker")) == 3 + assert len(hass.states.async_entity_ids(TRACKER_DOMAIN)) == 3 switch_1 = hass.states.get("switch.poe_client_1") switch_2 = hass.states.get("switch.poe_client_2") @@ -479,7 +481,7 @@ async def test_restoring_client(hass): """Test the update_items function with some clients.""" config_entry = config_entries.ConfigEntry( version=1, - domain=unifi.DOMAIN, + domain=UNIFI_DOMAIN, title="Mock Title", data=ENTRY_CONFIG, source="test", @@ -491,16 +493,16 @@ async def test_restoring_client(hass): registry = await entity_registry.async_get_registry(hass) registry.async_get_or_create( - switch.DOMAIN, - unifi.DOMAIN, - "poe-{}".format(CLIENT_1["mac"]), + SWITCH_DOMAIN, + UNIFI_DOMAIN, + f'{POE_SWITCH}-{CLIENT_1["mac"]}', suggested_object_id=CLIENT_1["hostname"], config_entry=config_entry, ) registry.async_get_or_create( - switch.DOMAIN, - unifi.DOMAIN, - "poe-{}".format(CLIENT_2["mac"]), + SWITCH_DOMAIN, + UNIFI_DOMAIN, + f'{POE_SWITCH}-{CLIENT_2["mac"]}', suggested_object_id=CLIENT_2["hostname"], config_entry=config_entry, ) @@ -518,7 +520,7 @@ async def test_restoring_client(hass): ) assert len(controller.mock_requests) == 4 - assert len(hass.states.async_entity_ids("switch")) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 device_1 = hass.states.get("switch.client_1") assert device_1 is not None From 4a5cf5cd2b01306bffc7a106ab7dff0388d4fc83 Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Thu, 23 Apr 2020 17:00:38 +0200 Subject: [PATCH 014/511] Powerwall sensor add is_active, round state attributes and change thresholding for charging status sensor (#34582) * Change sensor values and thresholding * Update tests --- .../components/powerwall/binary_sensor.py | 15 ++++------ homeassistant/components/powerwall/const.py | 10 ++----- homeassistant/components/powerwall/sensor.py | 12 ++++---- tests/components/powerwall/test_sensor.py | 30 +++++++++++-------- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/powerwall/binary_sensor.py b/homeassistant/components/powerwall/binary_sensor.py index 889e6c192ef..877efdd68fa 100644 --- a/homeassistant/components/powerwall/binary_sensor.py +++ b/homeassistant/components/powerwall/binary_sensor.py @@ -14,7 +14,6 @@ from .const import ( ATTR_GRID_CODE, ATTR_NOMINAL_SYSTEM_POWER, ATTR_REGION, - CHARGING_MARGIN_OF_ERROR, DOMAIN, POWERWALL_API_DEVICE_TYPE, POWERWALL_API_GRID_STATUS, @@ -139,7 +138,7 @@ class PowerWallGridStatusSensor(PowerWallEntity, BinarySensorDevice): class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorDevice): - """Representation of an Powerwall grid status sensor.""" + """Representation of an Powerwall charging status sensor.""" @property def name(self): @@ -158,10 +157,8 @@ class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorDevice): @property def is_on(self): - """Grid is online.""" - return ( - self._coordinator.data[POWERWALL_API_METERS][ - POWERWALL_BATTERY_METER - ].instant_power - < CHARGING_MARGIN_OF_ERROR - ) + """Powerwall is charging.""" + # is_sending_to returns true for values greater than 100 watts + return self._coordinator.data[POWERWALL_API_METERS][ + POWERWALL_BATTERY_METER + ].is_sending_to() diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index 91e501da28f..e2daf1e3760 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -10,10 +10,11 @@ UPDATE_INTERVAL = 30 ATTR_REGION = "region" ATTR_GRID_CODE = "grid_code" ATTR_FREQUENCY = "frequency" -ATTR_ENERGY_EXPORTED = "energy_exported" -ATTR_ENERGY_IMPORTED = "energy_imported" +ATTR_ENERGY_EXPORTED = "energy_exported_(in_kW)" +ATTR_ENERGY_IMPORTED = "energy_imported_(in_kW)" ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage" ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW" +ATTR_IS_ACTIVE = "is_active" SITE_INFO_UTILITY = "utility" SITE_INFO_GRID_CODE = "grid_code" @@ -44,11 +45,6 @@ POWERWALL_RUNNING_KEY = "running" POWERWALL_BATTERY_METER = "battery" -# We only declare charging if they are getting -# at least 40W incoming as measuring the fields -# is not an exact science because of interference -CHARGING_MARGIN_OF_ERROR = -40 - MODEL = "PowerWall 2" MANUFACTURER = "Tesla" diff --git a/homeassistant/components/powerwall/sensor.py b/homeassistant/components/powerwall/sensor.py index 253fae8bd36..e1e968c0353 100644 --- a/homeassistant/components/powerwall/sensor.py +++ b/homeassistant/components/powerwall/sensor.py @@ -1,7 +1,7 @@ """Support for August sensors.""" import logging -from tesla_powerwall import MeterType +from tesla_powerwall import MeterType, convert_to_kw from homeassistant.const import ( DEVICE_CLASS_BATTERY, @@ -14,6 +14,7 @@ from .const import ( ATTR_ENERGY_IMPORTED, ATTR_FREQUENCY, ATTR_INSTANT_AVERAGE_VOLTAGE, + ATTR_IS_ACTIVE, DOMAIN, ENERGY_KILO_WATT, POWERWALL_API_CHARGE, @@ -143,8 +144,9 @@ class PowerWallEnergySensor(PowerWallEntity): """Return the device specific state attributes.""" meter = self._coordinator.data[POWERWALL_API_METERS].get(self._meter) return { - ATTR_FREQUENCY: meter.frequency, - ATTR_ENERGY_EXPORTED: meter.energy_exported, - ATTR_ENERGY_IMPORTED: meter.energy_imported, - ATTR_INSTANT_AVERAGE_VOLTAGE: meter.instant_average_voltage, + ATTR_FREQUENCY: round(meter.frequency, 1), + ATTR_ENERGY_EXPORTED: convert_to_kw(meter.energy_exported), + ATTR_ENERGY_IMPORTED: convert_to_kw(meter.energy_imported), + ATTR_INSTANT_AVERAGE_VOLTAGE: round(meter.instant_average_voltage, 1), + ATTR_IS_ACTIVE: meter.is_active(), } diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index b6c224080a1..c68d9f0279e 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -36,12 +36,13 @@ async def test_sensors(hass): assert state.state == "0.032" expected_attributes = { "frequency": 60, - "energy_exported": 10429451.9916853, - "energy_imported": 4824191.60668611, - "instant_average_voltage": 120.650001525879, + "energy_exported_(in_kW)": 10429.5, + "energy_imported_(in_kW)": 4824.2, + "instant_average_voltage": 120.7, "unit_of_measurement": "kW", "friendly_name": "Powerwall Site Now", "device_class": "power", + "is_active": False, } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -52,12 +53,13 @@ async def test_sensors(hass): assert state.state == "1.971" expected_attributes = { "frequency": 60, - "energy_exported": 1056797.48917483, - "energy_imported": 4692987.91889705, - "instant_average_voltage": 120.650001525879, + "energy_exported_(in_kW)": 1056.8, + "energy_imported_(in_kW)": 4693.0, + "instant_average_voltage": 120.7, "unit_of_measurement": "kW", "friendly_name": "Powerwall Load Now", "device_class": "power", + "is_active": True, } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -67,13 +69,14 @@ async def test_sensors(hass): state = hass.states.get("sensor.powerwall_battery_now") assert state.state == "-8.55" expected_attributes = { - "frequency": 60.014, - "energy_exported": 3620010, - "energy_imported": 4216170, - "instant_average_voltage": 240.56, + "frequency": 60.0, + "energy_exported_(in_kW)": 3620.0, + "energy_imported_(in_kW)": 4216.2, + "instant_average_voltage": 240.6, "unit_of_measurement": "kW", "friendly_name": "Powerwall Battery Now", "device_class": "power", + "is_active": True, } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears @@ -84,12 +87,13 @@ async def test_sensors(hass): assert state.state == "10.49" expected_attributes = { "frequency": 60, - "energy_exported": 9864205.82222448, - "energy_imported": 28177.5358355867, - "instant_average_voltage": 120.685001373291, + "energy_exported_(in_kW)": 9864.2, + "energy_imported_(in_kW)": 28.2, + "instant_average_voltage": 120.7, "unit_of_measurement": "kW", "friendly_name": "Powerwall Solar Now", "device_class": "power", + "is_active": True, } # Only test for a subset of attributes in case # HA changes the implementation and a new one appears From 828f36832352531ffe79b452e6c536a635e72eee Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 23 Apr 2020 10:52:11 -0600 Subject: [PATCH 015/511] Handle flaky SimpliSafe notification registration (#34475) --- homeassistant/components/simplisafe/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index ae0e119c083..963195e6f64 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -428,20 +428,19 @@ class SimpliSafe: # ready. If that's the case, skip: return - old_notifications = self._system_notifications.get(system.system_id, []) - latest_notifications = system.notifications + latest_notifications = set(system.notifications) - # Save the latest notifications: - self._system_notifications[system.system_id] = latest_notifications - - # Process any notifications that are new: - to_add = set(latest_notifications) - set(old_notifications) + to_add = latest_notifications.difference( + self._system_notifications[system.system_id] + ) if not to_add: return _LOGGER.debug("New system notifications: %s", to_add) + self._system_notifications[system.system_id].update(to_add) + for notification in to_add: text = notification.text if notification.link: @@ -463,6 +462,8 @@ class SimpliSafe: self.systems = await self._api.get_systems() for system in self.systems.values(): + self._system_notifications[system.system_id] = set() + self._hass.async_create_task( async_register_base_station( self._hass, system, self._config_entry.entry_id From 3ce89409d2955b440f2c7b03d1ba8b6dbd383b16 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 23 Apr 2020 10:53:06 -0600 Subject: [PATCH 016/511] Add service to clear SimpliSafe notifications (#34481) --- homeassistant/components/simplisafe/__init__.py | 12 ++++++++++++ homeassistant/components/simplisafe/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 963195e6f64..63bf1f5b8fa 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -270,6 +270,17 @@ async def async_setup_entry(hass, config_entry): return decorator + @verify_system_exists + @_verify_domain_control + async def clear_notifications(call): + """Clear all active notifications.""" + system = simplisafe.systems[call.data[ATTR_SYSTEM_ID]] + try: + await system.clear_notifications() + except SimplipyError as err: + _LOGGER.error("Error during service call: %s", err) + return + @verify_system_exists @_verify_domain_control async def remove_pin(call): @@ -311,6 +322,7 @@ async def async_setup_entry(hass, config_entry): return for service, method, schema in [ + ("clear_notifications", clear_notifications, None), ("remove_pin", remove_pin, SERVICE_REMOVE_PIN_SCHEMA), ("set_pin", set_pin, SERVICE_SET_PIN_SCHEMA), ( diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index c03f05fc0c1..4fdf87ee88f 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.0.7"], + "requirements": ["simplisafe-python==9.1.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index faeaa3e0036..d64bdbd11f4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1885,7 +1885,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.0.7 +simplisafe-python==9.1.0 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2509a3d3935..c9543af1149 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -722,7 +722,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.0.7 +simplisafe-python==9.1.0 # homeassistant.components.sleepiq sleepyq==0.7 From a7e8446454973b967445d60388c7995ed2e1e70f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 Apr 2020 12:07:55 -0500 Subject: [PATCH 017/511] Remember homekit aids for entities without a unique id (#34587) * Remember homekit aids for entities without a unique id * add backwards compat * increase cover --- .../components/homekit/aidmanager.py | 62 ++- tests/components/homekit/test_aidmanager.py | 505 ++++++++++++++++++ 2 files changed, 545 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/homekit/aidmanager.py b/homeassistant/components/homekit/aidmanager.py index 61a922d17fa..95181114e79 100644 --- a/homeassistant/components/homekit/aidmanager.py +++ b/homeassistant/components/homekit/aidmanager.py @@ -25,6 +25,9 @@ AID_MANAGER_STORAGE_KEY = f"{DOMAIN}.aids" AID_MANAGER_STORAGE_VERSION = 1 AID_MANAGER_SAVE_DELAY = 2 +ALLOCATIONS_KEY = "allocations" +UNIQUE_IDS_KEY = "unique_ids" + INVALID_AIDS = (0, 1) AID_MIN = 2 @@ -46,10 +49,15 @@ def _generate_aids(unique_id: str, entity_id: str) -> int: # Not robust against collisions yield adler32(entity_id.encode("utf-8")) - # Use fnv1a_32 of the unique id as - # fnv1a_32 has less collisions than - # adler32 - yield fnv1a_32(unique_id.encode("utf-8")) + if unique_id: + # Use fnv1a_32 of the unique id as + # fnv1a_32 has less collisions than + # adler32 + yield fnv1a_32(unique_id.encode("utf-8")) + + # If there is no unique id we use + # fnv1a_32 as it is unlikely to collide + yield fnv1a_32(entity_id.encode("utf-8")) # If called again resort to random allocations. # Given the size of the range its unlikely we'll encounter duplicates @@ -86,34 +94,40 @@ class AccessoryAidStorage: # There is no data about aid allocations yet return - self.allocations = raw_storage.get("unique_ids", {}) + # Remove the UNIQUE_IDS_KEY in 0.112 and later + # The beta version used UNIQUE_IDS_KEY but + # since we now have entity ids in the dict + # we use ALLOCATIONS_KEY but check for + # UNIQUE_IDS_KEY in case the database has not + # been upgraded yet + self.allocations = raw_storage.get( + ALLOCATIONS_KEY, raw_storage.get(UNIQUE_IDS_KEY, {}) + ) self.allocated_aids = set(self.allocations.values()) def get_or_allocate_aid_for_entity_id(self, entity_id: str): """Generate a stable aid for an entity id.""" entity = self._entity_registry.async_get(entity_id) + if not entity: + return self._get_or_allocate_aid(None, entity_id) - if entity: - return self._get_or_allocate_aid( - get_system_unique_id(entity), entity.entity_id - ) - - _LOGGER.warning( - "Entity '%s' does not have a stable unique identifier so aid allocation will be unstable and may cause collisions", - entity_id, - ) - return adler32(entity_id.encode("utf-8")) + sys_unique_id = get_system_unique_id(entity) + return self._get_or_allocate_aid(sys_unique_id, entity_id) def _get_or_allocate_aid(self, unique_id: str, entity_id: str): """Allocate (and return) a new aid for an accessory.""" - if unique_id in self.allocations: - return self.allocations[unique_id] + # Prefer the unique_id over the + # entitiy_id + storage_key = unique_id or entity_id + + if storage_key in self.allocations: + return self.allocations[storage_key] for aid in _generate_aids(unique_id, entity_id): if aid in INVALID_AIDS: continue if aid not in self.allocated_aids: - self.allocations[unique_id] = aid + self.allocations[storage_key] = aid self.allocated_aids.add(aid) self.async_schedule_save() return aid @@ -122,12 +136,12 @@ class AccessoryAidStorage: f"Unable to generate unique aid allocation for {entity_id} [{unique_id}]" ) - def delete_aid(self, unique_id: str): + def delete_aid(self, storage_key: str): """Delete an aid allocation.""" - if unique_id not in self.allocations: + if storage_key not in self.allocations: return - aid = self.allocations.pop(unique_id) + aid = self.allocations.pop(storage_key) self.allocated_aids.discard(aid) self.async_schedule_save() @@ -136,7 +150,11 @@ class AccessoryAidStorage: """Schedule saving the entity map cache.""" self.store.async_delay_save(self._data_to_save, AID_MANAGER_SAVE_DELAY) + async def async_save(self): + """Save the entity map cache.""" + return await self.store.async_save(self._data_to_save()) + @callback def _data_to_save(self): """Return data of entity map to store in a file.""" - return {"unique_ids": self.allocations} + return {ALLOCATIONS_KEY: self.allocations} diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index bcadac953ab..12d12082a33 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -1,12 +1,17 @@ """Tests for the HomeKit AID manager.""" +import os +from zlib import adler32 + from asynctest import patch import pytest from homeassistant.components.homekit.aidmanager import ( + AID_MANAGER_STORAGE_KEY, AccessoryAidStorage, get_system_unique_id, ) from homeassistant.helpers import device_registry +from homeassistant.helpers.storage import STORAGE_DIR from tests.common import MockConfigEntry, mock_device_registry, mock_registry @@ -118,3 +123,503 @@ async def test_aid_adler32_collision(hass, device_reg, entity_reg): aid = aid_storage.get_or_allocate_aid_for_entity_id(ent.entity_id) assert aid not in seen_aids seen_aids.add(aid) + + +async def test_aid_generation_no_unique_ids_handles_collision( + hass, device_reg, entity_reg +): + """Test colliding aids is stable.""" + + aid_storage = AccessoryAidStorage(hass) + await aid_storage.async_initialize() + + seen_aids = set() + collisions = [] + + for light_id in range(0, 220): + entity_id = f"light.light{light_id}" + hass.states.async_set(entity_id, "on") + expected_aid = adler32(entity_id.encode("utf-8")) + aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id) + if aid != expected_aid: + collisions.append(entity_id) + + assert aid not in seen_aids + seen_aids.add(aid) + + assert collisions == [ + "light.light201", + "light.light202", + "light.light203", + "light.light204", + "light.light205", + "light.light206", + "light.light207", + "light.light208", + "light.light209", + "light.light211", + "light.light212", + "light.light213", + "light.light214", + "light.light215", + "light.light216", + "light.light217", + "light.light218", + "light.light219", + ] + + assert aid_storage.allocations == { + "light.light0": 514851983, + "light.light1": 514917520, + "light.light10": 594609344, + "light.light100": 677446896, + "light.light101": 677512433, + "light.light102": 677577970, + "light.light103": 677643507, + "light.light104": 677709044, + "light.light105": 677774581, + "light.light106": 677840118, + "light.light107": 677905655, + "light.light108": 677971192, + "light.light109": 678036729, + "light.light11": 594674881, + "light.light110": 677577969, + "light.light111": 677643506, + "light.light112": 677709043, + "light.light113": 677774580, + "light.light114": 677840117, + "light.light115": 677905654, + "light.light116": 677971191, + "light.light117": 678036728, + "light.light118": 678102265, + "light.light119": 678167802, + "light.light12": 594740418, + "light.light120": 677709042, + "light.light121": 677774579, + "light.light122": 677840116, + "light.light123": 677905653, + "light.light124": 677971190, + "light.light125": 678036727, + "light.light126": 678102264, + "light.light127": 678167801, + "light.light128": 678233338, + "light.light129": 678298875, + "light.light13": 594805955, + "light.light130": 677840115, + "light.light131": 677905652, + "light.light132": 677971189, + "light.light133": 678036726, + "light.light134": 678102263, + "light.light135": 678167800, + "light.light136": 678233337, + "light.light137": 678298874, + "light.light138": 678364411, + "light.light139": 678429948, + "light.light14": 594871492, + "light.light140": 677971188, + "light.light141": 678036725, + "light.light142": 678102262, + "light.light143": 678167799, + "light.light144": 678233336, + "light.light145": 678298873, + "light.light146": 678364410, + "light.light147": 678429947, + "light.light148": 678495484, + "light.light149": 678561021, + "light.light15": 594937029, + "light.light150": 678102261, + "light.light151": 678167798, + "light.light152": 678233335, + "light.light153": 678298872, + "light.light154": 678364409, + "light.light155": 678429946, + "light.light156": 678495483, + "light.light157": 678561020, + "light.light158": 678626557, + "light.light159": 678692094, + "light.light16": 595002566, + "light.light160": 678233334, + "light.light161": 678298871, + "light.light162": 678364408, + "light.light163": 678429945, + "light.light164": 678495482, + "light.light165": 678561019, + "light.light166": 678626556, + "light.light167": 678692093, + "light.light168": 678757630, + "light.light169": 678823167, + "light.light17": 595068103, + "light.light170": 678364407, + "light.light171": 678429944, + "light.light172": 678495481, + "light.light173": 678561018, + "light.light174": 678626555, + "light.light175": 678692092, + "light.light176": 678757629, + "light.light177": 678823166, + "light.light178": 678888703, + "light.light179": 678954240, + "light.light18": 595133640, + "light.light180": 678495480, + "light.light181": 678561017, + "light.light182": 678626554, + "light.light183": 678692091, + "light.light184": 678757628, + "light.light185": 678823165, + "light.light186": 678888702, + "light.light187": 678954239, + "light.light188": 679019776, + "light.light189": 679085313, + "light.light19": 595199177, + "light.light190": 678626553, + "light.light191": 678692090, + "light.light192": 678757627, + "light.light193": 678823164, + "light.light194": 678888701, + "light.light195": 678954238, + "light.light196": 679019775, + "light.light197": 679085312, + "light.light198": 679150849, + "light.light199": 679216386, + "light.light2": 514983057, + "light.light20": 594740417, + "light.light200": 677643505, + "light.light201": 1682157970, + "light.light202": 1665380351, + "light.light203": 1648602732, + "light.light204": 1631825113, + "light.light205": 1615047494, + "light.light206": 1598269875, + "light.light207": 1581492256, + "light.light208": 1833156541, + "light.light209": 1816378922, + "light.light21": 594805954, + "light.light210": 677774578, + "light.light211": 1614900399, + "light.light212": 1631678018, + "light.light213": 1648455637, + "light.light214": 1531012304, + "light.light215": 1547789923, + "light.light216": 1564567542, + "light.light217": 1581345161, + "light.light218": 1732343732, + "light.light219": 1749121351, + "light.light22": 594871491, + "light.light23": 594937028, + "light.light24": 595002565, + "light.light25": 595068102, + "light.light26": 595133639, + "light.light27": 595199176, + "light.light28": 595264713, + "light.light29": 595330250, + "light.light3": 515048594, + "light.light30": 594871490, + "light.light31": 594937027, + "light.light32": 595002564, + "light.light33": 595068101, + "light.light34": 595133638, + "light.light35": 595199175, + "light.light36": 595264712, + "light.light37": 595330249, + "light.light38": 595395786, + "light.light39": 595461323, + "light.light4": 515114131, + "light.light40": 595002563, + "light.light41": 595068100, + "light.light42": 595133637, + "light.light43": 595199174, + "light.light44": 595264711, + "light.light45": 595330248, + "light.light46": 595395785, + "light.light47": 595461322, + "light.light48": 595526859, + "light.light49": 595592396, + "light.light5": 515179668, + "light.light50": 595133636, + "light.light51": 595199173, + "light.light52": 595264710, + "light.light53": 595330247, + "light.light54": 595395784, + "light.light55": 595461321, + "light.light56": 595526858, + "light.light57": 595592395, + "light.light58": 595657932, + "light.light59": 595723469, + "light.light6": 515245205, + "light.light60": 595264709, + "light.light61": 595330246, + "light.light62": 595395783, + "light.light63": 595461320, + "light.light64": 595526857, + "light.light65": 595592394, + "light.light66": 595657931, + "light.light67": 595723468, + "light.light68": 595789005, + "light.light69": 595854542, + "light.light7": 515310742, + "light.light70": 595395782, + "light.light71": 595461319, + "light.light72": 595526856, + "light.light73": 595592393, + "light.light74": 595657930, + "light.light75": 595723467, + "light.light76": 595789004, + "light.light77": 595854541, + "light.light78": 595920078, + "light.light79": 595985615, + "light.light8": 515376279, + "light.light80": 595526855, + "light.light81": 595592392, + "light.light82": 595657929, + "light.light83": 595723466, + "light.light84": 595789003, + "light.light85": 595854540, + "light.light86": 595920077, + "light.light87": 595985614, + "light.light88": 596051151, + "light.light89": 596116688, + "light.light9": 515441816, + "light.light90": 595657928, + "light.light91": 595723465, + "light.light92": 595789002, + "light.light93": 595854539, + "light.light94": 595920076, + "light.light95": 595985613, + "light.light96": 596051150, + "light.light97": 596116687, + "light.light98": 596182224, + "light.light99": 596247761, + } + + await aid_storage.async_save() + await hass.async_block_till_done() + + aid_storage = AccessoryAidStorage(hass) + await aid_storage.async_initialize() + + assert aid_storage.allocations == { + "light.light0": 514851983, + "light.light1": 514917520, + "light.light10": 594609344, + "light.light100": 677446896, + "light.light101": 677512433, + "light.light102": 677577970, + "light.light103": 677643507, + "light.light104": 677709044, + "light.light105": 677774581, + "light.light106": 677840118, + "light.light107": 677905655, + "light.light108": 677971192, + "light.light109": 678036729, + "light.light11": 594674881, + "light.light110": 677577969, + "light.light111": 677643506, + "light.light112": 677709043, + "light.light113": 677774580, + "light.light114": 677840117, + "light.light115": 677905654, + "light.light116": 677971191, + "light.light117": 678036728, + "light.light118": 678102265, + "light.light119": 678167802, + "light.light12": 594740418, + "light.light120": 677709042, + "light.light121": 677774579, + "light.light122": 677840116, + "light.light123": 677905653, + "light.light124": 677971190, + "light.light125": 678036727, + "light.light126": 678102264, + "light.light127": 678167801, + "light.light128": 678233338, + "light.light129": 678298875, + "light.light13": 594805955, + "light.light130": 677840115, + "light.light131": 677905652, + "light.light132": 677971189, + "light.light133": 678036726, + "light.light134": 678102263, + "light.light135": 678167800, + "light.light136": 678233337, + "light.light137": 678298874, + "light.light138": 678364411, + "light.light139": 678429948, + "light.light14": 594871492, + "light.light140": 677971188, + "light.light141": 678036725, + "light.light142": 678102262, + "light.light143": 678167799, + "light.light144": 678233336, + "light.light145": 678298873, + "light.light146": 678364410, + "light.light147": 678429947, + "light.light148": 678495484, + "light.light149": 678561021, + "light.light15": 594937029, + "light.light150": 678102261, + "light.light151": 678167798, + "light.light152": 678233335, + "light.light153": 678298872, + "light.light154": 678364409, + "light.light155": 678429946, + "light.light156": 678495483, + "light.light157": 678561020, + "light.light158": 678626557, + "light.light159": 678692094, + "light.light16": 595002566, + "light.light160": 678233334, + "light.light161": 678298871, + "light.light162": 678364408, + "light.light163": 678429945, + "light.light164": 678495482, + "light.light165": 678561019, + "light.light166": 678626556, + "light.light167": 678692093, + "light.light168": 678757630, + "light.light169": 678823167, + "light.light17": 595068103, + "light.light170": 678364407, + "light.light171": 678429944, + "light.light172": 678495481, + "light.light173": 678561018, + "light.light174": 678626555, + "light.light175": 678692092, + "light.light176": 678757629, + "light.light177": 678823166, + "light.light178": 678888703, + "light.light179": 678954240, + "light.light18": 595133640, + "light.light180": 678495480, + "light.light181": 678561017, + "light.light182": 678626554, + "light.light183": 678692091, + "light.light184": 678757628, + "light.light185": 678823165, + "light.light186": 678888702, + "light.light187": 678954239, + "light.light188": 679019776, + "light.light189": 679085313, + "light.light19": 595199177, + "light.light190": 678626553, + "light.light191": 678692090, + "light.light192": 678757627, + "light.light193": 678823164, + "light.light194": 678888701, + "light.light195": 678954238, + "light.light196": 679019775, + "light.light197": 679085312, + "light.light198": 679150849, + "light.light199": 679216386, + "light.light2": 514983057, + "light.light20": 594740417, + "light.light200": 677643505, + "light.light201": 1682157970, + "light.light202": 1665380351, + "light.light203": 1648602732, + "light.light204": 1631825113, + "light.light205": 1615047494, + "light.light206": 1598269875, + "light.light207": 1581492256, + "light.light208": 1833156541, + "light.light209": 1816378922, + "light.light21": 594805954, + "light.light210": 677774578, + "light.light211": 1614900399, + "light.light212": 1631678018, + "light.light213": 1648455637, + "light.light214": 1531012304, + "light.light215": 1547789923, + "light.light216": 1564567542, + "light.light217": 1581345161, + "light.light218": 1732343732, + "light.light219": 1749121351, + "light.light22": 594871491, + "light.light23": 594937028, + "light.light24": 595002565, + "light.light25": 595068102, + "light.light26": 595133639, + "light.light27": 595199176, + "light.light28": 595264713, + "light.light29": 595330250, + "light.light3": 515048594, + "light.light30": 594871490, + "light.light31": 594937027, + "light.light32": 595002564, + "light.light33": 595068101, + "light.light34": 595133638, + "light.light35": 595199175, + "light.light36": 595264712, + "light.light37": 595330249, + "light.light38": 595395786, + "light.light39": 595461323, + "light.light4": 515114131, + "light.light40": 595002563, + "light.light41": 595068100, + "light.light42": 595133637, + "light.light43": 595199174, + "light.light44": 595264711, + "light.light45": 595330248, + "light.light46": 595395785, + "light.light47": 595461322, + "light.light48": 595526859, + "light.light49": 595592396, + "light.light5": 515179668, + "light.light50": 595133636, + "light.light51": 595199173, + "light.light52": 595264710, + "light.light53": 595330247, + "light.light54": 595395784, + "light.light55": 595461321, + "light.light56": 595526858, + "light.light57": 595592395, + "light.light58": 595657932, + "light.light59": 595723469, + "light.light6": 515245205, + "light.light60": 595264709, + "light.light61": 595330246, + "light.light62": 595395783, + "light.light63": 595461320, + "light.light64": 595526857, + "light.light65": 595592394, + "light.light66": 595657931, + "light.light67": 595723468, + "light.light68": 595789005, + "light.light69": 595854542, + "light.light7": 515310742, + "light.light70": 595395782, + "light.light71": 595461319, + "light.light72": 595526856, + "light.light73": 595592393, + "light.light74": 595657930, + "light.light75": 595723467, + "light.light76": 595789004, + "light.light77": 595854541, + "light.light78": 595920078, + "light.light79": 595985615, + "light.light8": 515376279, + "light.light80": 595526855, + "light.light81": 595592392, + "light.light82": 595657929, + "light.light83": 595723466, + "light.light84": 595789003, + "light.light85": 595854540, + "light.light86": 595920077, + "light.light87": 595985614, + "light.light88": 596051151, + "light.light89": 596116688, + "light.light9": 515441816, + "light.light90": 595657928, + "light.light91": 595723465, + "light.light92": 595789002, + "light.light93": 595854539, + "light.light94": 595920076, + "light.light95": 595985613, + "light.light96": 596051150, + "light.light97": 596116687, + "light.light98": 596182224, + "light.light99": 596247761, + } + + aid_storage_path = hass.config.path(STORAGE_DIR, AID_MANAGER_STORAGE_KEY) + if await hass.async_add_executor_job(os.path.exists, aid_storage_path): + await hass.async_add_executor_job(os.unlink, aid_storage_path) From b578a76efa96c24295741a03cf74c7f0b14c3de0 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 23 Apr 2020 21:29:38 +0200 Subject: [PATCH 018/511] UniFi - Move some preloading of unavailable clients earlier in setup phase (#34599) Improve readability of setting up switch platform --- homeassistant/components/unifi/controller.py | 24 +++++++++ .../components/unifi/device_tracker.py | 20 ------- homeassistant/components/unifi/switch.py | 54 ++++++++----------- tests/components/unifi/test_switch.py | 4 +- 4 files changed, 48 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 807e727777c..8ccb42794ec 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -265,6 +265,30 @@ class UniFiController: LOGGER.error("Unknown error connecting with UniFi controller: %s", err) return False + # Restore clients that is 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 + ): + continue + + mac = "" + if entity.domain == TRACKER_DOMAIN: + mac, _ = entity.unique_id.split("-", 1) + elif entity.domain == SWITCH_DOMAIN: + _, mac = entity.unique_id.split("-", 1) + + if mac in self.api.clients or mac not in self.api.clients_all: + continue + + client = self.api.clients_all[mac] + self.api.clients.process_raw([client.raw]) + LOGGER.debug( + "Restore disconnected client %s (%s)", entity.entity_id, client.mac, + ) + wireless_clients = self.hass.data[UNIFI_WIRELESS_CLIENTS] self.wireless_clients = wireless_clients.get_data(self.config_entry) self.update_wireless_clients() diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 4b72c520b31..7bc1bbd3197 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -47,26 +47,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] controller.entities[DOMAIN] = {CLIENT_TRACKER: set(), DEVICE_TRACKER: set()} - # Restore clients that is not a part of active clients list. - entity_registry = await hass.helpers.entity_registry.async_get_registry() - for entity in entity_registry.entities.values(): - - if ( - entity.config_entry_id == config_entry.entry_id - and entity.domain == DOMAIN - and "-" in entity.unique_id - ): - - mac, _ = entity.unique_id.split("-", 1) - if mac in controller.api.clients or mac not in controller.api.clients_all: - continue - - client = controller.api.clients_all[mac] - controller.api.clients.process_raw([client.raw]) - LOGGER.debug( - "Restore disconnected client %s (%s)", entity.entity_id, client.mac, - ) - @callback def items_added(): """Update the values of the controller.""" diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index 73e1dd131bc..b562b8bd746 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -30,63 +30,53 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if controller.site_role != "admin": return - switches_off = [] - - # Restore clients that is not a part of active clients list. + # Store previously known POE control entities in case their POE are turned off. + previously_known_poe_clients = [] entity_registry = await hass.helpers.entity_registry.async_get_registry() for entity in entity_registry.entities.values(): if ( - entity.config_entry_id == config_entry.entry_id - and entity.unique_id.startswith(f"{POE_SWITCH}-") + entity.config_entry_id != config_entry.entry_id + or not entity.unique_id.startswith(POE_SWITCH) ): + continue - _, mac = entity.unique_id.split("-", 1) + 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) - if mac in controller.api.clients: - switches_off.append(entity.unique_id) - continue - - if mac in controller.api.clients_all: - client = controller.api.clients_all[mac] - controller.api.clients.process_raw([client.raw]) - switches_off.append(entity.unique_id) - continue + for mac in controller.option_block_clients: + if mac not in controller.api.clients and mac in controller.api.clients_all: + client = controller.api.clients_all[mac] + controller.api.clients.process_raw([client.raw]) @callback def items_added(): """Update the values of the controller.""" if controller.option_block_clients or controller.option_poe_clients: - add_entities(controller, async_add_entities, switches_off) + add_entities(controller, async_add_entities, previously_known_poe_clients) for signal in (controller.signal_update, controller.signal_options_update): controller.listeners.append(async_dispatcher_connect(hass, signal, items_added)) items_added() - switches_off.clear() + previously_known_poe_clients.clear() @callback -def add_entities(controller, async_add_entities, switches_off): +def add_entities(controller, async_add_entities, previously_known_poe_clients): """Add new switch entities from the controller.""" switches = [] for mac in controller.option_block_clients: - if mac in controller.entities[DOMAIN][BLOCK_SWITCH]: - continue - - client = None - - if mac in controller.api.clients: - client = controller.api.clients[mac] - - elif mac in controller.api.clients_all: - client = controller.api.clients_all[mac] - - if not client: + if ( + mac in controller.entities[DOMAIN][BLOCK_SWITCH] + or mac not in controller.api.clients + ): continue + client = controller.api.clients[mac] switches.append(UniFiBlockClientSwitch(client, controller)) if controller.option_poe_clients: @@ -101,7 +91,7 @@ def add_entities(controller, async_add_entities, switches_off): client = controller.api.clients[mac] - if poe_client_id not in switches_off and ( + if poe_client_id not in previously_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 @@ -114,7 +104,7 @@ def add_entities(controller, async_add_entities, switches_off): multi_clients_on_port = False for client2 in controller.api.clients.values(): - if poe_client_id in switches_off: + if poe_client_id in previously_known_poe_clients: break if ( diff --git a/tests/components/unifi/test_switch.py b/tests/components/unifi/test_switch.py index 4dfd3bc4c9d..506fa14377c 100644 --- a/tests/components/unifi/test_switch.py +++ b/tests/components/unifi/test_switch.py @@ -383,7 +383,7 @@ async def test_option_block_clients(hass): options={CONF_BLOCK_CLIENT: [BLOCKED["mac"], UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 # Remove the second switch again hass.config_entries.async_update_entry( @@ -397,7 +397,7 @@ async def test_option_block_clients(hass): controller.config_entry, options={CONF_BLOCK_CLIENT: [UNBLOCKED["mac"]]}, ) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1 + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 0 # Remove one hass.config_entries.async_update_entry( From 0869d209d792cd45d955127bfa7e24aa4beff5c4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 21:33:15 +0200 Subject: [PATCH 019/511] Upgrade pre-commit to 2.3.0 (#34604) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8b4b5d0edcf..4949034b6d9 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,7 +7,7 @@ asynctest==0.13.0 codecov==2.0.15 mock-open==1.3.1 mypy==0.770 -pre-commit==2.2.0 +pre-commit==2.3.0 pylint==2.4.4 astroid==2.3.3 pylint-strict-informational==0.1 From 36413e112fe8208ca72a1333c51bd7fd19e7329a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 21:33:37 +0200 Subject: [PATCH 020/511] Fix Garmin Connect i/o in event loop (#34598) --- homeassistant/components/garmin_connect/__init__.py | 2 +- homeassistant/components/garmin_connect/config_flow.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/garmin_connect/__init__.py b/homeassistant/components/garmin_connect/__init__.py index 1536a875698..8abdbbbbae9 100644 --- a/homeassistant/components/garmin_connect/__init__.py +++ b/homeassistant/components/garmin_connect/__init__.py @@ -38,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): garmin_client = Garmin(username, password) try: - garmin_client.login() + await hass.async_add_executor_job(garmin_client.login) except ( GarminConnectAuthenticationError, GarminConnectTooManyRequestsError, diff --git a/homeassistant/components/garmin_connect/config_flow.py b/homeassistant/components/garmin_connect/config_flow.py index 36c63c7b995..e0b50fa371b 100644 --- a/homeassistant/components/garmin_connect/config_flow.py +++ b/homeassistant/components/garmin_connect/config_flow.py @@ -42,7 +42,7 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors = {} try: - garmin_client.login() + await self.hass.async_add_executor_job(garmin_client.login) except GarminConnectConnectionError: errors["base"] = "cannot_connect" return await self._show_setup_form(errors) From 4fa268ecb412be7e7ab2c62eb3b0588bdb47275d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 23 Apr 2020 21:42:01 +0200 Subject: [PATCH 021/511] Limit clone/view stats to repos with push access (#34575) --- homeassistant/components/github/sensor.py | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/github/sensor.py b/homeassistant/components/github/sensor.py index 68b5586207a..dcd81dc68df 100644 --- a/homeassistant/components/github/sensor.py +++ b/homeassistant/components/github/sensor.py @@ -133,13 +133,17 @@ class GitHubSensor(Entity): ATTR_OPEN_PULL_REQUESTS: self._pull_request_count, ATTR_STARGAZERS: self._stargazers, ATTR_FORKS: self._forks, - ATTR_CLONES: self._clones, - ATTR_CLONES_UNIQUE: self._clones_unique, - ATTR_VIEWS: self._views, - ATTR_VIEWS_UNIQUE: self._views_unique, } if self._latest_release_tag is not None: attrs[ATTR_LATEST_RELEASE_TAG] = self._latest_release_tag + if self._clones is not None: + attrs[ATTR_CLONES] = self._clones + if self._clones_unique is not None: + attrs[ATTR_CLONES_UNIQUE] = self._clones_unique + if self._views is not None: + attrs[ATTR_VIEWS] = self._views + if self._views_unique is not None: + attrs[ATTR_VIEWS_UNIQUE] = self._views_unique return attrs @property @@ -244,15 +248,16 @@ class GitHubData: if releases and releases.totalCount > 0: self.latest_release_url = releases[0].html_url - clones = repo.get_clones_traffic() - if clones is not None: - self.clones = clones.get("count") - self.clones_unique = clones.get("uniques") + if repo.permissions.push: + clones = repo.get_clones_traffic() + if clones is not None: + self.clones = clones.get("count") + self.clones_unique = clones.get("uniques") - views = repo.get_views_traffic() - if views is not None: - self.views = views.get("count") - self.views_unique = views.get("uniques") + views = repo.get_views_traffic() + if views is not None: + self.views = views.get("count") + self.views_unique = views.get("uniques") self.available = True except self._github.GithubException as err: From f922128a14c976eeafe297fd8da47a34f25f1382 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 23 Apr 2020 14:42:17 -0500 Subject: [PATCH 022/511] Add Plex play_media logging and troubleshooting tools (#34412) --- homeassistant/components/plex/media_player.py | 44 ++++++++++++++----- homeassistant/components/plex/server.py | 4 ++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index d34899a6213..79d1339bde1 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -7,9 +7,12 @@ import requests.exceptions from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerDevice from homeassistant.components.media_player.const import ( + MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_TVSHOW, + MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, @@ -553,27 +556,44 @@ class PlexMediaPlayer(MediaPlayerDevice): def play_media(self, media_type, media_id, **kwargs): """Play a piece of media.""" if not (self.device and "playback" in self._device_protocol_capabilities): + _LOGGER.debug( + "Client is not currently accepting playback controls: %s", self.name + ) return + media_type = media_type.lower() src = json.loads(media_id) - library = src.get("library_name") - shuffle = src.get("shuffle", 0) + if media_type == PLEX_DOMAIN and isinstance(src, int): + try: + media = self.plex_server.fetch_item(src) + except plexapi.exceptions.NotFound: + _LOGGER.error("Media for key %s not found", src) + return + shuffle = 0 + else: + library = src.get("library_name") + shuffle = src.get("shuffle", 0) + media = None - media = None - - if media_type == "MUSIC": - media = self._get_music_media(library, src) - elif media_type == "EPISODE": - media = self._get_tv_media(library, src) - elif media_type == "PLAYLIST": - media = self.plex_server.playlist(src["playlist_name"]) - elif media_type == "VIDEO": - media = self.plex_server.library.section(library).get(src["video_name"]) + try: + if media_type == MEDIA_TYPE_MUSIC: + media = self._get_music_media(library, src) + elif media_type == MEDIA_TYPE_EPISODE: + media = self._get_tv_media(library, src) + elif media_type == MEDIA_TYPE_PLAYLIST: + media = self.plex_server.playlist(src["playlist_name"]) + elif media_type == MEDIA_TYPE_VIDEO: + media = self.plex_server.library.section(library).get(src["video_name"]) + except plexapi.exceptions.NotFound: + _LOGGER.error("Media could not be found: %s", media_id) + return if media is None: _LOGGER.error("Media could not be found: %s", media_id) return + _LOGGER.debug("Attempting to play %s on %s", media, self.name) + playqueue = self.plex_server.create_playqueue(media, shuffle=shuffle) try: self.device.playMedia(playqueue) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index d9e2d2bd9cc..ca5e4756cf2 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -332,3 +332,7 @@ class PlexServer: def create_playqueue(self, media, **kwargs): """Create playqueue on Plex server.""" return plexapi.playqueue.PlayQueue.create(self._plex_server, media, **kwargs) + + def fetch_item(self, item): + """Fetch item from Plex server.""" + return self._plex_server.fetchItem(item) From 0b64f49e3af027ba9d1e052d74701e4da8de1c2e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 21:53:22 +0200 Subject: [PATCH 023/511] Remove old style translations from Atag (#34585) --- .../components/atag/.translations/en.json | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 homeassistant/components/atag/.translations/en.json diff --git a/homeassistant/components/atag/.translations/en.json b/homeassistant/components/atag/.translations/en.json deleted file mode 100644 index 094fde70dc9..00000000000 --- a/homeassistant/components/atag/.translations/en.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "title": "Atag", - "config": { - "step": { - "user": { - "title": "Connect to the device", - "data": { - "host": "Host", - "port": "Port (10000)" - } - } - }, - "error": { - "connection_error": "Failed to connect, please try again" - }, - "abort": { - "already_configured": "Only one Atag device can be added to Home Assistant" - } - } -} From b022e08db9b722af39fe34ebd543d81be8e1744d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 23 Apr 2020 21:57:07 +0200 Subject: [PATCH 024/511] Rename BinarySensorDevice to BinarySensorEntity (#34462) * Rename BinarySensorDevice to BinarySensorEntity * Tweak * Move deprecation warning to __new__, add test * Move deprecation warning back to __init__ * Move deprecation warning to __init_subclass --- .../components/abode/binary_sensor.py | 4 +- homeassistant/components/ads/binary_sensor.py | 4 +- .../components/alarmdecoder/binary_sensor.py | 4 +- .../ambient_station/binary_sensor.py | 4 +- .../components/amcrest/binary_sensor.py | 4 +- .../android_ip_webcam/binary_sensor.py | 4 +- .../components/apcupsd/binary_sensor.py | 4 +- .../components/arest/binary_sensor.py | 4 +- .../components/august/binary_sensor.py | 6 +-- .../components/aurora/binary_sensor.py | 4 +- .../components/axis/binary_sensor.py | 4 +- .../components/bayesian/binary_sensor.py | 4 +- .../components/bbb_gpio/binary_sensor.py | 4 +- .../components/binary_sensor/__init__.py | 16 +++++++- .../components/blink/binary_sensor.py | 4 +- .../components/bloomsky/binary_sensor.py | 4 +- .../bmw_connected_drive/binary_sensor.py | 4 +- .../components/cloud/binary_sensor.py | 4 +- .../components/command_line/binary_sensor.py | 4 +- .../components/concord232/binary_sensor.py | 4 +- .../components/danfoss_air/binary_sensor.py | 4 +- .../components/deconz/binary_sensor.py | 4 +- .../components/demo/binary_sensor.py | 4 +- .../components/digital_ocean/binary_sensor.py | 4 +- .../components/ecobee/binary_sensor.py | 4 +- .../components/egardia/binary_sensor.py | 4 +- .../components/eight_sleep/binary_sensor.py | 4 +- .../components/enocean/binary_sensor.py | 4 +- .../components/envisalink/binary_sensor.py | 4 +- .../components/esphome/binary_sensor.py | 4 +- .../components/ffmpeg_motion/binary_sensor.py | 4 +- .../components/fibaro/binary_sensor.py | 4 +- .../components/flic/binary_sensor.py | 4 +- .../components/fritzbox/binary_sensor.py | 4 +- .../components/gc100/binary_sensor.py | 4 +- .../components/geniushub/binary_sensor.py | 4 +- .../components/hikvision/binary_sensor.py | 4 +- .../components/hive/binary_sensor.py | 4 +- .../homekit_controller/binary_sensor.py | 12 +++--- .../components/homematic/binary_sensor.py | 6 +-- .../homematicip_cloud/binary_sensor.py | 30 +++++++-------- .../components/huawei_lte/binary_sensor.py | 4 +- homeassistant/components/hue/binary_sensor.py | 4 +- .../components/hydrawise/binary_sensor.py | 4 +- .../components/iaqualink/binary_sensor.py | 4 +- homeassistant/components/ihc/binary_sensor.py | 4 +- .../components/incomfort/binary_sensor.py | 4 +- .../components/insteon/binary_sensor.py | 4 +- homeassistant/components/iss/binary_sensor.py | 4 +- .../components/isy994/binary_sensor.py | 12 +++--- .../jewish_calendar/binary_sensor.py | 4 +- .../components/keba/binary_sensor.py | 4 +- homeassistant/components/knx/binary_sensor.py | 4 +- .../components/konnected/binary_sensor.py | 4 +- homeassistant/components/lcn/binary_sensor.py | 8 ++-- .../components/linode/binary_sensor.py | 4 +- .../components/lupusec/binary_sensor.py | 4 +- .../components/lutron/binary_sensor.py | 4 +- .../components/lutron_caseta/binary_sensor.py | 4 +- .../components/maxcube/binary_sensor.py | 8 ++-- .../components/mcp23017/binary_sensor.py | 4 +- .../components/meteoalarm/binary_sensor.py | 4 +- .../minecraft_server/binary_sensor.py | 4 +- .../components/mobile_app/binary_sensor.py | 4 +- .../components/modbus/binary_sensor.py | 4 +- .../components/mqtt/binary_sensor.py | 4 +- .../components/mychevy/binary_sensor.py | 4 +- homeassistant/components/myq/binary_sensor.py | 6 +-- .../components/mysensors/binary_sensor.py | 4 +- .../components/mystrom/binary_sensor.py | 4 +- .../components/ness_alarm/binary_sensor.py | 4 +- .../components/nest/binary_sensor.py | 4 +- .../components/netgear_lte/binary_sensor.py | 4 +- .../components/nexia/binary_sensor.py | 4 +- .../components/nextcloud/binary_sensor.py | 4 +- .../components/nissan_leaf/binary_sensor.py | 6 +-- .../components/notion/binary_sensor.py | 4 +- .../components/nx584/binary_sensor.py | 4 +- .../components/octoprint/binary_sensor.py | 4 +- .../components/opentherm_gw/binary_sensor.py | 4 +- .../components/openuv/binary_sensor.py | 4 +- .../components/orangepi_gpio/binary_sensor.py | 4 +- .../components/pcal9535a/binary_sensor.py | 4 +- .../components/pi4ioe5v9xxxx/binary_sensor.py | 4 +- .../components/pilight/binary_sensor.py | 6 +-- .../components/ping/binary_sensor.py | 4 +- .../components/point/binary_sensor.py | 4 +- .../components/powerwall/binary_sensor.py | 10 ++--- .../components/proxmoxve/binary_sensor.py | 4 +- .../components/qwikswitch/binary_sensor.py | 4 +- .../components/rachio/binary_sensor.py | 4 +- .../components/rainbird/binary_sensor.py | 4 +- .../components/raincloud/binary_sensor.py | 4 +- .../components/rainmachine/binary_sensor.py | 4 +- .../components/random/binary_sensor.py | 4 +- .../components/raspihats/binary_sensor.py | 4 +- .../remote_rpi_gpio/binary_sensor.py | 4 +- .../components/rest/binary_sensor.py | 4 +- .../components/rflink/binary_sensor.py | 4 +- .../components/rfxtrx/binary_sensor.py | 4 +- .../components/ring/binary_sensor.py | 4 +- .../components/roomba/binary_sensor.py | 4 +- .../components/rpi_gpio/binary_sensor.py | 4 +- .../components/rpi_pfio/binary_sensor.py | 4 +- .../components/satel_integra/binary_sensor.py | 4 +- .../components/sense/binary_sensor.py | 4 +- .../components/skybell/binary_sensor.py | 4 +- .../components/sleepiq/binary_sensor.py | 4 +- .../components/smartthings/binary_sensor.py | 4 +- .../components/smarty/binary_sensor.py | 4 +- homeassistant/components/spc/binary_sensor.py | 4 +- .../components/starline/binary_sensor.py | 4 +- .../components/stookalert/binary_sensor.py | 4 +- .../streamlabswater/binary_sensor.py | 4 +- .../components/surepetcare/binary_sensor.py | 8 ++-- .../components/tahoma/binary_sensor.py | 4 +- .../components/tapsaff/binary_sensor.py | 4 +- homeassistant/components/tcp/binary_sensor.py | 4 +- .../components/tellduslive/binary_sensor.py | 4 +- .../components/template/binary_sensor.py | 4 +- .../components/tesla/binary_sensor.py | 4 +- .../components/threshold/binary_sensor.py | 4 +- homeassistant/components/tod/binary_sensor.py | 4 +- .../components/toon/binary_sensor.py | 4 +- .../components/totalconnect/binary_sensor.py | 4 +- .../components/trend/binary_sensor.py | 4 +- .../components/upcloud/binary_sensor.py | 4 +- .../components/updater/binary_sensor.py | 4 +- .../components/uptimerobot/binary_sensor.py | 4 +- .../components/velbus/binary_sensor.py | 4 +- .../components/vera/binary_sensor.py | 4 +- .../components/verisure/binary_sensor.py | 6 +-- .../components/volvooncall/binary_sensor.py | 4 +- .../components/vultr/binary_sensor.py | 4 +- .../components/w800rf32/binary_sensor.py | 4 +- .../components/wemo/binary_sensor.py | 4 +- .../components/wink/binary_sensor.py | 22 +++++------ .../components/wirelesstag/binary_sensor.py | 4 +- .../components/workday/binary_sensor.py | 4 +- .../components/xiaomi_aqara/binary_sensor.py | 4 +- .../components/yeelight/binary_sensor.py | 4 +- homeassistant/components/zha/binary_sensor.py | 4 +- .../components/zigbee/binary_sensor.py | 4 +- .../components/zoneminder/binary_sensor.py | 4 +- .../components/zwave/binary_sensor.py | 4 +- tests/components/binary_sensor/test_init.py | 37 +++++++++++-------- .../custom_components/test/binary_sensor.py | 4 +- 147 files changed, 371 insertions(+), 352 deletions(-) diff --git a/homeassistant/components/abode/binary_sensor.py b/homeassistant/components/abode/binary_sensor.py index 916ed2e2613..7175fbc550a 100644 --- a/homeassistant/components/abode/binary_sensor.py +++ b/homeassistant/components/abode/binary_sensor.py @@ -3,7 +3,7 @@ import abodepy.helpers.constants as CONST from homeassistant.components.binary_sensor import ( DEVICE_CLASS_WINDOW, - BinarySensorDevice, + BinarySensorEntity, ) from . import AbodeDevice @@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeBinarySensor(AbodeDevice, BinarySensorDevice): +class AbodeBinarySensor(AbodeDevice, BinarySensorEntity): """A binary sensor implementation for Abode device.""" @property diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index 9e2f7b0cc4a..df8a74dc1d5 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ads_sensor]) -class AdsBinarySensor(AdsEntity, BinarySensorDevice): +class AdsBinarySensor(AdsEntity, BinarySensorEntity): """Representation of ADS binary sensors.""" def __init__(self, ads_hub, name, ads_var, device_class): diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index b34c90bc35a..cec1b8356b0 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -1,7 +1,7 @@ """Support for AlarmDecoder zone states- represented as binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import ( CONF_RELAY_ADDR, @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class AlarmDecoderBinarySensor(BinarySensorDevice): +class AlarmDecoderBinarySensor(BinarySensorEntity): """Representation of an AlarmDecoder binary sensor.""" def __init__( diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py index d1b1f9b8f1d..5aba9d637d4 100644 --- a/homeassistant/components/ambient_station/binary_sensor.py +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Ambient Weather Station binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_NAME from homeassistant.core import callback @@ -54,7 +54,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(binary_sensor_list, True) -class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice): +class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorEntity): """Define an Ambient binary sensor.""" @property diff --git a/homeassistant/components/amcrest/binary_sensor.py b/homeassistant/components/amcrest/binary_sensor.py index 40cb755bd98..a3057211f2a 100644 --- a/homeassistant/components/amcrest/binary_sensor.py +++ b/homeassistant/components/amcrest/binary_sensor.py @@ -7,7 +7,7 @@ from amcrest import AmcrestError from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_MOTION, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_BINARY_SENSORS, CONF_NAME from homeassistant.core import callback @@ -63,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class AmcrestBinarySensor(BinarySensorDevice): +class AmcrestBinarySensor(BinarySensorEntity): """Binary sensor for Amcrest camera.""" def __init__(self, name, device, sensor_type): diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index 0e9cca46afb..14384565718 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Android IP Webcam binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity @@ -16,7 +16,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([IPWebcamBinarySensor(name, host, ipcam, "motion_active")], True) -class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): +class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorEntity): """Representation of an IP Webcam binary sensor.""" def __init__(self, name, host, ipcam, sensor): diff --git a/homeassistant/components/apcupsd/binary_sensor.py b/homeassistant/components/apcupsd/binary_sensor.py index 000e738052d..daf9592f3e6 100644 --- a/homeassistant/components/apcupsd/binary_sensor.py +++ b/homeassistant/components/apcupsd/binary_sensor.py @@ -1,7 +1,7 @@ """Support for tracking the online status of a UPS.""" import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -20,7 +20,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([OnlineStatus(config, apcups_data)], True) -class OnlineStatus(BinarySensorDevice): +class OnlineStatus(BinarySensorEntity): """Representation of an UPS online status.""" def __init__(self, config, data): diff --git a/homeassistant/components/arest/binary_sensor.py b/homeassistant/components/arest/binary_sensor.py index 1b914f80aa7..3cd9038f1a8 100644 --- a/homeassistant/components/arest/binary_sensor.py +++ b/homeassistant/components/arest/binary_sensor.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( CONF_DEVICE_CLASS, @@ -67,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ArestBinarySensor(BinarySensorDevice): +class ArestBinarySensor(BinarySensorEntity): """Implement an aREST binary sensor for a pin.""" def __init__(self, arest, resource, name, device_class, pin): diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index e61de730302..6602cfe8661 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -11,7 +11,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_DOOR, DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.core import callback from homeassistant.helpers.event import async_track_point_in_utc_time @@ -108,7 +108,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices, True) -class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorDevice): +class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorEntity): """Representation of an August Door binary sensor.""" def __init__(self, data, sensor_type, device): @@ -155,7 +155,7 @@ class AugustDoorBinarySensor(AugustEntityMixin, BinarySensorDevice): return f"{self._device_id}_open" -class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorDevice): +class AugustDoorbellBinarySensor(AugustEntityMixin, BinarySensorEntity): """Representation of an August binary sensor.""" def __init__(self, data, sensor_type, device): diff --git a/homeassistant/components/aurora/binary_sensor.py b/homeassistant/components/aurora/binary_sensor.py index e2f07276c6c..1d5a6e83ec1 100644 --- a/homeassistant/components/aurora/binary_sensor.py +++ b/homeassistant/components/aurora/binary_sensor.py @@ -7,7 +7,7 @@ from aiohttp.hdrs import USER_AGENT import requests import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([AuroraSensor(aurora_data, name)], True) -class AuroraSensor(BinarySensorDevice): +class AuroraSensor(BinarySensorEntity): """Implementation of an aurora sensor.""" def __init__(self, aurora_data, name): diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index d992c28746c..4709d706ad0 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -4,7 +4,7 @@ from datetime import timedelta from axis.event_stream import CLASS_INPUT, CLASS_OUTPUT -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_TRIGGER_TIME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AxisBinarySensor(AxisEventBase, BinarySensorDevice): +class AxisBinarySensor(AxisEventBase, BinarySensorEntity): """Representation of a binary Axis event.""" def __init__(self, event, device): diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index b51653bb3c8..c4150131901 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -3,7 +3,7 @@ from collections import OrderedDict import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( CONF_ABOVE, CONF_BELOW, @@ -113,7 +113,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class BayesianBinarySensor(BinarySensorDevice): +class BayesianBinarySensor(BinarySensorEntity): """Representation of a Bayesian sensor.""" def __init__(self, name, prior, observations, probability_threshold, device_class): diff --git a/homeassistant/components/bbb_gpio/binary_sensor.py b/homeassistant/components/bbb_gpio/binary_sensor.py index b1245aeabde..229f7a6c61e 100644 --- a/homeassistant/components/bbb_gpio/binary_sensor.py +++ b/homeassistant/components/bbb_gpio/binary_sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import bbb_gpio -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors) -class BBBGPIOBinarySensor(BinarySensorDevice): +class BBBGPIOBinarySensor(BinarySensorEntity): """Representation of a binary sensor that uses Beaglebone Black GPIO.""" def __init__(self, pin, params): diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 7dc5d958537..f022509f9de 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -15,6 +15,8 @@ from homeassistant.helpers.entity_component import EntityComponent # mypy: allow-untyped-defs, no-check-untyped-defs +_LOGGER = logging.getLogger(__name__) + DOMAIN = "binary_sensor" SCAN_INTERVAL = timedelta(seconds=30) @@ -142,7 +144,7 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class BinarySensorDevice(Entity): +class BinarySensorEntity(Entity): """Represent a binary sensor.""" @property @@ -159,3 +161,15 @@ class BinarySensorDevice(Entity): def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" return None + + +class BinarySensorDevice(BinarySensorEntity): + """Represent a binary sensor (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "BinarySensorDevice is deprecated, modify %s to extend BinarySensorEntity", + cls.__name__, + ) diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index e8c01953bff..219b9fb8cd3 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Blink system camera control.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS from . import BINARY_SENSORS, BLINK_DATA @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs, True) -class BlinkBinarySensor(BinarySensorDevice): +class BlinkBinarySensor(BinarySensorEntity): """Representation of a Blink binary sensor.""" def __init__(self, data, camera, sensor_type): diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index 140d7e638a7..b98bb688ca3 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -33,7 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BloomSkySensor(bloomsky, device, variable)], True) -class BloomSkySensor(BinarySensorDevice): +class BloomSkySensor(BinarySensorEntity): """Representation of a single binary sensor in a BloomSky device.""" def __init__(self, bs, device, sensor_name): diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index fc3069f284c..ee89873e8fe 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -3,7 +3,7 @@ import logging from bimmer_connected.state import ChargingState, LockState -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_KILOMETERS from . import DOMAIN as BMW_DOMAIN @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class BMWConnectedDriveSensor(BinarySensorDevice): +class BMWConnectedDriveSensor(BinarySensorEntity): """Representation of a BMW vehicle binary sensor.""" def __init__( diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py index c2974678faa..baa63679d42 100644 --- a/homeassistant/components/cloud/binary_sensor.py +++ b/homeassistant/components/cloud/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Home Assistant Cloud binary sensors.""" import asyncio -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN @@ -18,7 +18,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([CloudRemoteBinary(cloud)]) -class CloudRemoteBinary(BinarySensorDevice): +class CloudRemoteBinary(BinarySensorEntity): """Representation of an Cloud Remote UI Connection binary sensor.""" def __init__(self, cloud): diff --git a/homeassistant/components/command_line/binary_sensor.py b/homeassistant/components/command_line/binary_sensor.py index eaa371be1a3..dc62d8daa9d 100644 --- a/homeassistant/components/command_line/binary_sensor.py +++ b/homeassistant/components/command_line/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( CONF_COMMAND, @@ -68,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class CommandBinarySensor(BinarySensorDevice): +class CommandBinarySensor(BinarySensorEntity): """Representation of a command line binary sensor.""" def __init__( diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py index 326ac799f06..3077056c397 100644 --- a/homeassistant/components/concord232/binary_sensor.py +++ b/homeassistant/components/concord232/binary_sensor.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -95,7 +95,7 @@ def get_opening_type(zone): return "opening" -class Concord232ZoneSensor(BinarySensorDevice): +class Concord232ZoneSensor(BinarySensorEntity): """Representation of a Concord232 zone as a sensor.""" def __init__(self, hass, client, zone, zone_type): diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index caac12c1b20..7f6876a709b 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -1,7 +1,7 @@ """Support for the for Danfoss Air HRV binary sensors.""" from pydanfossair.commands import ReadCommand -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -23,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DanfossAirBinarySensor(BinarySensorDevice): +class DanfossAirBinarySensor(BinarySensorEntity): """Representation of a Danfoss Air binary sensor.""" def __init__(self, data, name, sensor_type, device_class): diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index d16722525f9..95fa223c697 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -1,7 +1,7 @@ """Support for deCONZ binary sensors.""" from pydeconz.sensor import Presence, Vibration -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -53,7 +53,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class DeconzBinarySensor(DeconzDevice, BinarySensorDevice): +class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): """Representation of a deCONZ binary sensor.""" @callback diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index 0f6dfa9f357..04d8e72f9a8 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -1,5 +1,5 @@ """Demo platform that has two fake binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoBinarySensor(BinarySensorDevice): +class DemoBinarySensor(BinarySensorEntity): """representation of a Demo binary sensor.""" def __init__(self, unique_id, name, state, device_class): diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index c3515177535..d076dae9210 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv @@ -50,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DigitalOceanBinarySensor(BinarySensorDevice): +class DigitalOceanBinarySensor(BinarySensorEntity): """Representation of a Digital Ocean droplet sensor.""" def __init__(self, do, droplet_id): diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 2f422007ff4..64c4b07ed1f 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Ecobee binary sensors.""" from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, - BinarySensorDevice, + BinarySensorEntity, ) from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER @@ -22,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(dev, True) -class EcobeeBinarySensor(BinarySensorDevice): +class EcobeeBinarySensor(BinarySensorEntity): """Representation of an Ecobee sensor.""" def __init__(self, data, sensor_name, sensor_index): diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 4f02d6fdde0..4be443a36f4 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -1,7 +1,7 @@ """Interfaces with Egardia/Woonveilig alarm control panel.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import STATE_OFF, STATE_ON from . import ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE @@ -38,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class EgardiaBinarySensor(BinarySensorDevice): +class EgardiaBinarySensor(BinarySensorEntity): """Represents a sensor based on an Egardia sensor (IR, Door Contact).""" def __init__(self, sensor_id, name, egardia_system, device_class): diff --git a/homeassistant/components/eight_sleep/binary_sensor.py b/homeassistant/components/eight_sleep/binary_sensor.py index 7b801578ccd..803b20383b6 100644 --- a/homeassistant/components/eight_sleep/binary_sensor.py +++ b/homeassistant/components/eight_sleep/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Eight Sleep binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import CONF_BINARY_SENSORS, DATA_EIGHT, NAME_MAP, EightSleepHeatEntity @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_sensors, True) -class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice): +class EightHeatSensor(EightSleepHeatEntity, BinarySensorEntity): """Representation of a Eight Sleep heat-based sensor.""" def __init__(self, name, eight, sensor): diff --git a/homeassistant/components/enocean/binary_sensor.py b/homeassistant/components/enocean/binary_sensor.py index 4ff1b461129..7fb8ea5e3f2 100644 --- a/homeassistant/components/enocean/binary_sensor.py +++ b/homeassistant/components/enocean/binary_sensor.py @@ -7,7 +7,7 @@ from homeassistant.components import enocean from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanBinarySensor(dev_id, dev_name, device_class)]) -class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): +class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorEntity): """Representation of EnOcean binary sensors such as wall switches. Supported EEPs (EnOcean Equipment Profiles): diff --git a/homeassistant/components/envisalink/binary_sensor.py b/homeassistant/components/envisalink/binary_sensor.py index f698a9d27d9..54445660484 100644 --- a/homeassistant/components/envisalink/binary_sensor.py +++ b/homeassistant/components/envisalink/binary_sensor.py @@ -2,7 +2,7 @@ import datetime import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_LAST_TRIP_TIME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): +class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorEntity): """Representation of an Envisalink binary sensor.""" def __init__(self, hass, zone_number, zone_name, zone_type, info, controller): diff --git a/homeassistant/components/esphome/binary_sensor.py b/homeassistant/components/esphome/binary_sensor.py index fe41bb2f7bb..d605a48410b 100644 --- a/homeassistant/components/esphome/binary_sensor.py +++ b/homeassistant/components/esphome/binary_sensor.py @@ -3,7 +3,7 @@ from typing import Optional from aioesphomeapi import BinarySensorInfo, BinarySensorState -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import EsphomeEntity, platform_async_setup_entry @@ -21,7 +21,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice): +class EsphomeBinarySensor(EsphomeEntity, BinarySensorEntity): """A binary sensor implementation for ESPHome.""" @property diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index e3a9c09b5d9..a8842f9c401 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -4,7 +4,7 @@ import logging import haffmpeg.sensor as ffmpeg_sensor import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.components.ffmpeg import ( CONF_EXTRA_ARGUMENTS, CONF_INITIAL_STATE, @@ -55,7 +55,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([entity]) -class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice): +class FFmpegBinarySensor(FFmpegBase, BinarySensorEntity): """A binary sensor which use FFmpeg for noise detection.""" def __init__(self, config): diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index fa2d6ceb3c6..251bd1df6a3 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Fibaro binary sensors.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON from . import FIBARO_DEVICES, FibaroDevice @@ -33,7 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroBinarySensor(FibaroDevice, BinarySensorDevice): +class FibaroBinarySensor(FibaroDevice, BinarySensorEntity): """Representation of a Fibaro Binary Sensor.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/flic/binary_sensor.py b/homeassistant/components/flic/binary_sensor.py index 55f92e2e5ce..e81f8f2f5b0 100644 --- a/homeassistant/components/flic/binary_sensor.py +++ b/homeassistant/components/flic/binary_sensor.py @@ -12,7 +12,7 @@ from pyflic import ( ) import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( CONF_DISCOVERY, CONF_HOST, @@ -123,7 +123,7 @@ def setup_button(hass, config, add_entities, client, address): add_entities([button]) -class FlicButton(BinarySensorDevice): +class FlicButton(BinarySensorEntity): """Representation of a flic button.""" def __init__(self, hass, client, address, timeout, ignored_click_types): diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index c0893b93316..7db216c32e1 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Fritzbox binary sensors.""" import requests -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_DEVICES from .const import CONF_CONNECTIONS, DOMAIN as FRITZBOX_DOMAIN, LOGGER @@ -21,7 +21,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class FritzboxBinarySensor(BinarySensorDevice): +class FritzboxBinarySensor(BinarySensorEntity): """Representation of a binary Fritzbox device.""" def __init__(self, device, fritz): diff --git a/homeassistant/components/gc100/binary_sensor.py b/homeassistant/components/gc100/binary_sensor.py index a2f8ba4a0a2..43ceb75e449 100644 --- a/homeassistant/components/gc100/binary_sensor.py +++ b/homeassistant/components/gc100/binary_sensor.py @@ -1,7 +1,7 @@ """Support for binary sensor using GC100.""" import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors, True) -class GC100BinarySensor(BinarySensorDevice): +class GC100BinarySensor(BinarySensorEntity): """Representation of a binary sensor from GC100.""" def __init__(self, name, port_addr, gc100): diff --git a/homeassistant/components/geniushub/binary_sensor.py b/homeassistant/components/geniushub/binary_sensor.py index 33458d049a2..d935192f97d 100644 --- a/homeassistant/components/geniushub/binary_sensor.py +++ b/homeassistant/components/geniushub/binary_sensor.py @@ -1,5 +1,5 @@ """Support for Genius Hub binary_sensor devices.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import DOMAIN, GeniusDevice @@ -25,7 +25,7 @@ async def async_setup_platform( async_add_entities(switches, update_before_add=True) -class GeniusBinarySensor(GeniusDevice, BinarySensorDevice): +class GeniusBinarySensor(GeniusDevice, BinarySensorEntity): """Representation of a Genius Hub binary_sensor.""" def __init__(self, broker, device, state_attr) -> None: diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index 4d2a879bc73..779afa10cca 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -5,7 +5,7 @@ import logging from pyhik.hikvision import HikCamera import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE, @@ -183,7 +183,7 @@ class HikvisionData: return self.camdata.fetch_attributes(sensor, channel) -class HikvisionBinarySensor(BinarySensorDevice): +class HikvisionBinarySensor(BinarySensorEntity): """Representation of a Hikvision binary sensor.""" def __init__(self, hass, sensor, channel, cam, delay): diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index fa91d6862a2..27c648f554b 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,5 +1,5 @@ """Support for the Hive binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DATA_HIVE, DOMAIN, HiveEntity @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class HiveBinarySensorEntity(HiveEntity, BinarySensorDevice): +class HiveBinarySensorEntity(HiveEntity, BinarySensorEntity): """Representation of a Hive binary sensor.""" @property diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index b96e5f651e3..939c6055e10 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -9,7 +9,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, DEVICE_CLASS_OPENING, DEVICE_CLASS_SMOKE, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.core import callback @@ -18,7 +18,7 @@ from . import KNOWN_DEVICES, HomeKitEntity _LOGGER = logging.getLogger(__name__) -class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): +class HomeKitMotionSensor(HomeKitEntity, BinarySensorEntity): """Representation of a Homekit motion sensor.""" def get_characteristic_types(self): @@ -36,7 +36,7 @@ class HomeKitMotionSensor(HomeKitEntity, BinarySensorDevice): return self.service.value(CharacteristicsTypes.MOTION_DETECTED) -class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): +class HomeKitContactSensor(HomeKitEntity, BinarySensorEntity): """Representation of a Homekit contact sensor.""" def get_characteristic_types(self): @@ -54,7 +54,7 @@ class HomeKitContactSensor(HomeKitEntity, BinarySensorDevice): return self.service.value(CharacteristicsTypes.CONTACT_STATE) == 1 -class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice): +class HomeKitSmokeSensor(HomeKitEntity, BinarySensorEntity): """Representation of a Homekit smoke sensor.""" @property @@ -72,7 +72,7 @@ class HomeKitSmokeSensor(HomeKitEntity, BinarySensorDevice): return self.service.value(CharacteristicsTypes.SMOKE_DETECTED) == 1 -class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice): +class HomeKitOccupancySensor(HomeKitEntity, BinarySensorEntity): """Representation of a Homekit occupancy sensor.""" @property @@ -90,7 +90,7 @@ class HomeKitOccupancySensor(HomeKitEntity, BinarySensorDevice): return self.service.value(CharacteristicsTypes.OCCUPANCY_DETECTED) == 1 -class HomeKitLeakSensor(HomeKitEntity, BinarySensorDevice): +class HomeKitLeakSensor(HomeKitEntity, BinarySensorEntity): """Representation of a Homekit leak sensor.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py index 731525c8460..041f2f02643 100644 --- a/homeassistant/components/homematic/binary_sensor.py +++ b/homeassistant/components/homematic/binary_sensor.py @@ -7,7 +7,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OPENING, DEVICE_CLASS_PRESENCE, DEVICE_CLASS_SMOKE, - BinarySensorDevice, + BinarySensorEntity, ) from .const import ATTR_DISCOVER_DEVICES, ATTR_DISCOVERY_TYPE, DISCOVER_BATTERY @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMBinarySensor(HMDevice, BinarySensorDevice): +class HMBinarySensor(HMDevice, BinarySensorEntity): """Representation of a binary HomeMatic device.""" @property @@ -73,7 +73,7 @@ class HMBinarySensor(HMDevice, BinarySensorDevice): self._data.update({self._state: None}) -class HMBatterySensor(HMDevice, BinarySensorDevice): +class HMBatterySensor(HMDevice, BinarySensorEntity): """Representation of an HomeMatic low battery sensor.""" @property diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 00147e1b7ec..15c41be24b5 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -36,7 +36,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_PRESENCE, DEVICE_CLASS_SAFETY, DEVICE_CLASS_SMOKE, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -131,7 +131,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipAccelerationSensor(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipAccelerationSensor(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud acceleration sensor.""" @property @@ -157,7 +157,7 @@ class HomematicipAccelerationSensor(HomematicipGenericDevice, BinarySensorDevice return state_attr -class HomematicipContactInterface(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipContactInterface(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud contact interface.""" @property @@ -173,7 +173,7 @@ class HomematicipContactInterface(HomematicipGenericDevice, BinarySensorDevice): return self._device.windowState != WindowState.CLOSED -class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud shutter contact.""" @property @@ -189,7 +189,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice): return self._device.windowState != WindowState.CLOSED -class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud motion detector.""" @property @@ -203,7 +203,7 @@ class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice): return self._device.motionDetected -class HomematicipPresenceDetector(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipPresenceDetector(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud presence detector.""" @property @@ -217,7 +217,7 @@ class HomematicipPresenceDetector(HomematicipGenericDevice, BinarySensorDevice): return self._device.presenceDetected -class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud smoke detector.""" @property @@ -236,7 +236,7 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice): return False -class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud water detector.""" @property @@ -250,7 +250,7 @@ class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice): return self._device.moistureDetected or self._device.waterlevelDetected -class HomematicipStormSensor(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipStormSensor(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud storm sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -268,7 +268,7 @@ class HomematicipStormSensor(HomematicipGenericDevice, BinarySensorDevice): return self._device.storm -class HomematicipRainSensor(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipRainSensor(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud rain sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -286,7 +286,7 @@ class HomematicipRainSensor(HomematicipGenericDevice, BinarySensorDevice): return self._device.raining -class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud sunshine sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -315,7 +315,7 @@ class HomematicipSunshineSensor(HomematicipGenericDevice, BinarySensorDevice): return state_attr -class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud low battery sensor.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -334,7 +334,7 @@ class HomematicipBatterySensor(HomematicipGenericDevice, BinarySensorDevice): class HomematicipPluggableMainsFailureSurveillanceSensor( - HomematicipGenericDevice, BinarySensorDevice + HomematicipGenericDevice, BinarySensorEntity ): """Representation of a HomematicIP Cloud pluggable mains failure surveillance sensor.""" @@ -353,7 +353,7 @@ class HomematicipPluggableMainsFailureSurveillanceSensor( return not self._device.powerMainsFailure -class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, BinarySensorDevice): +class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, BinarySensorEntity): """Representation of a HomematicIP Cloud security zone group.""" def __init__(self, hap: HomematicipHAP, device, post: str = "SecurityZone") -> None: @@ -409,7 +409,7 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericDevice, BinarySensorD class HomematicipSecuritySensorGroup( - HomematicipSecurityZoneSensorGroup, BinarySensorDevice + HomematicipSecurityZoneSensorGroup, BinarySensorEntity ): """Representation of a HomematicIP security group.""" diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index af6ed75d591..575cc9789ca 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -8,7 +8,7 @@ from huawei_lte_api.enums.cradle import ConnectionStatusEnum from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_URL @@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @attr.s -class HuaweiLteBaseBinarySensor(HuaweiLteBaseEntity, BinarySensorDevice): +class HuaweiLteBaseBinarySensor(HuaweiLteBaseEntity, BinarySensorEntity): """Huawei LTE binary sensor device base class.""" key: str diff --git a/homeassistant/components/hue/binary_sensor.py b/homeassistant/components/hue/binary_sensor.py index 8a6b5d203a8..cfbe041aafe 100644 --- a/homeassistant/components/hue/binary_sensor.py +++ b/homeassistant/components/hue/binary_sensor.py @@ -4,7 +4,7 @@ from aiohue.sensors import TYPE_ZLL_PRESENCE from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOTION, - BinarySensorDevice, + BinarySensorEntity, ) from .const import DOMAIN as HUE_DOMAIN @@ -20,7 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ].sensor_manager.async_register_component("binary_sensor", async_add_entities) -class HuePresence(GenericZLLSensor, BinarySensorDevice): +class HuePresence(GenericZLLSensor, BinarySensorEntity): """The presence sensor entity for a Hue motion sensor device.""" device_class = DEVICE_CLASS_MOTION diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 037c48b029e..389506c6d5a 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorDevice): +class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity): """A sensor implementation for Hydrawise device.""" @property diff --git a/homeassistant/components/iaqualink/binary_sensor.py b/homeassistant/components/iaqualink/binary_sensor.py index 30d419c1bce..669188c473f 100644 --- a/homeassistant/components/iaqualink/binary_sensor.py +++ b/homeassistant/components/iaqualink/binary_sensor.py @@ -4,7 +4,7 @@ import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_COLD, DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -27,7 +27,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkBinarySensor(AqualinkEntity, BinarySensorDevice): +class HassAqualinkBinarySensor(AqualinkEntity, BinarySensorEntity): """Representation of a binary sensor.""" @property diff --git a/homeassistant/components/ihc/binary_sensor.py b/homeassistant/components/ihc/binary_sensor.py index 3f59d7981fb..0c6a685bc93 100644 --- a/homeassistant/components/ihc/binary_sensor.py +++ b/homeassistant/components/ihc/binary_sensor.py @@ -1,5 +1,5 @@ """Support for IHC binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_TYPE from . import IHC_CONTROLLER, IHC_INFO @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class IHCBinarySensor(IHCDevice, BinarySensorDevice): +class IHCBinarySensor(IHCDevice, BinarySensorEntity): """IHC Binary Sensor. The associated IHC resource can be any in or output from a IHC product diff --git a/homeassistant/components/incomfort/binary_sensor.py b/homeassistant/components/incomfort/binary_sensor.py index f15c2298b9d..bf1340fb235 100644 --- a/homeassistant/components/incomfort/binary_sensor.py +++ b/homeassistant/components/incomfort/binary_sensor.py @@ -3,7 +3,7 @@ from typing import Any, Dict, Optional from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from . import DOMAIN, IncomfortChild @@ -20,7 +20,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([IncomfortFailed(client, h) for h in heaters]) -class IncomfortFailed(IncomfortChild, BinarySensorDevice): +class IncomfortFailed(IncomfortChild, BinarySensorEntity): """Representation of an InComfort Failed sensor.""" def __init__(self, client, heater) -> None: diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index b96feb21831..81c3c58ef12 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -1,7 +1,7 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from .insteon_entity import InsteonEntity @@ -38,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([new_entity]) -class InsteonBinarySensor(InsteonEntity, BinarySensorDevice): +class InsteonBinarySensor(InsteonEntity, BinarySensorEntity): """A Class for an Insteon device entity.""" def __init__(self, device, state_key): diff --git a/homeassistant/components/iss/binary_sensor.py b/homeassistant/components/iss/binary_sensor.py index 3b8e222c912..e1f0d7a19ce 100644 --- a/homeassistant/components/iss/binary_sensor.py +++ b/homeassistant/components/iss/binary_sensor.py @@ -6,7 +6,7 @@ import pyiss import requests import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( ATTR_LATITUDE, ATTR_LONGITUDE, @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([IssBinarySensor(iss_data, name, show_on_map)], True) -class IssBinarySensor(BinarySensorDevice): +class IssBinarySensor(BinarySensorEntity): """Implementation of the ISS binary sensor.""" def __init__(self, iss_data, name, show): diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 30b26ea5d24..a96f2f44fdb 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging from typing import Callable -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback from homeassistant.helpers.event import async_track_point_in_utc_time @@ -31,7 +31,7 @@ def setup_platform( for node in hass.data[ISY994_NODES][DOMAIN]: if node.parent_node is None: - device = ISYBinarySensorDevice(node) + device = ISYBinarySensorEntity(node) devices.append(device) devices_by_nid[node.nid] = device else: @@ -66,7 +66,7 @@ def setup_platform( else: # We don't yet have any special logic for other sensor types, # so add the nodes as individual devices - device = ISYBinarySensorDevice(node) + device = ISYBinarySensorEntity(node) devices.append(device) for name, status, _ in hass.data[ISY994_PROGRAMS][DOMAIN]: @@ -95,7 +95,7 @@ def _is_val_unknown(val): return val == -1 * float("inf") -class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): +class ISYBinarySensorEntity(ISYDevice, BinarySensorEntity): """Representation of an ISY994 binary sensor device. Often times, a single device is represented by multiple nodes in the ISY, @@ -253,7 +253,7 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): return self._device_class_from_type -class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice): +class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorEntity): """Representation of the battery state of an ISY994 sensor.""" def __init__(self, node, parent_device) -> None: @@ -353,7 +353,7 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice): return attr -class ISYBinarySensorProgram(ISYDevice, BinarySensorDevice): +class ISYBinarySensorProgram(ISYDevice, BinarySensorEntity): """Representation of an ISY994 binary sensor program. This does not need all of the subnode logic in the device version of binary diff --git a/homeassistant/components/jewish_calendar/binary_sensor.py b/homeassistant/components/jewish_calendar/binary_sensor.py index 7362fce3cd0..22e6a46e0ec 100644 --- a/homeassistant/components/jewish_calendar/binary_sensor.py +++ b/homeassistant/components/jewish_calendar/binary_sensor.py @@ -3,7 +3,7 @@ import logging import hdate -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity import homeassistant.util.dt as dt_util from . import DOMAIN, SENSOR_TYPES @@ -24,7 +24,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class JewishCalendarBinarySensor(BinarySensorDevice): +class JewishCalendarBinarySensor(BinarySensorEntity): """Representation of an Jewish Calendar binary sensor.""" def __init__(self, data, sensor, sensor_info): diff --git a/homeassistant/components/keba/binary_sensor.py b/homeassistant/components/keba/binary_sensor.py index 5cced416bc3..5c9edfa7793 100644 --- a/homeassistant/components/keba/binary_sensor.py +++ b/homeassistant/components/keba/binary_sensor.py @@ -6,7 +6,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_PLUG, DEVICE_CLASS_POWER, DEVICE_CLASS_SAFETY, - BinarySensorDevice, + BinarySensorEntity, ) from . import DOMAIN @@ -36,7 +36,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class KebaBinarySensor(BinarySensorDevice): +class KebaBinarySensor(BinarySensorEntity): """Representation of a binary sensor of a KEBA charging station.""" def __init__(self, keba, key, name, entity_type, device_class): diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 95e7e2cc400..9f8c3cc8491 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -2,7 +2,7 @@ import voluptuous as vol from xknx.devices import BinarySensor -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -102,7 +102,7 @@ def async_add_entities_config(hass, config, async_add_entities): async_add_entities([entity]) -class KNXBinarySensor(BinarySensorDevice): +class KNXBinarySensor(BinarySensorEntity): """Representation of a KNX binary sensor.""" def __init__(self, device): diff --git a/homeassistant/components/konnected/binary_sensor.py b/homeassistant/components/konnected/binary_sensor.py index 5cd270d5008..20494eee424 100644 --- a/homeassistant/components/konnected/binary_sensor.py +++ b/homeassistant/components/konnected/binary_sensor.py @@ -1,7 +1,7 @@ """Support for wired binary sensors attached to a Konnected device.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_STATE, @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class KonnectedBinarySensor(BinarySensorDevice): +class KonnectedBinarySensor(BinarySensorEntity): """Representation of a Konnected binary sensor.""" def __init__(self, device_id, zone_num, data): diff --git a/homeassistant/components/lcn/binary_sensor.py b/homeassistant/components/lcn/binary_sensor.py index 249adf04af8..7b4cedfebad 100644 --- a/homeassistant/components/lcn/binary_sensor.py +++ b/homeassistant/components/lcn/binary_sensor.py @@ -1,7 +1,7 @@ """Support for LCN binary sensors.""" import pypck -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_ADDRESS from . import LcnDevice @@ -36,7 +36,7 @@ async def async_setup_platform( async_add_entities(devices) -class LcnRegulatorLockSensor(LcnDevice, BinarySensorDevice): +class LcnRegulatorLockSensor(LcnDevice, BinarySensorEntity): """Representation of a LCN binary sensor for regulator locks.""" def __init__(self, config, address_connection): @@ -71,7 +71,7 @@ class LcnRegulatorLockSensor(LcnDevice, BinarySensorDevice): self.async_write_ha_state() -class LcnBinarySensor(LcnDevice, BinarySensorDevice): +class LcnBinarySensor(LcnDevice, BinarySensorEntity): """Representation of a LCN binary sensor for binary sensor ports.""" def __init__(self, config, address_connection): @@ -103,7 +103,7 @@ class LcnBinarySensor(LcnDevice, BinarySensorDevice): self.async_write_ha_state() -class LcnLockKeysSensor(LcnDevice, BinarySensorDevice): +class LcnLockKeysSensor(LcnDevice, BinarySensorEntity): """Representation of a LCN sensor for key locks.""" def __init__(self, config, address_connection): diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index 674a97a0f45..c4a14210d32 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity import homeassistant.helpers.config_validation as cv from . import ( @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class LinodeBinarySensor(BinarySensorDevice): +class LinodeBinarySensor(BinarySensorEntity): """Representation of a Linode droplet sensor.""" def __init__(self, li, node_id): diff --git a/homeassistant/components/lupusec/binary_sensor.py b/homeassistant/components/lupusec/binary_sensor.py index b2a332a03e7..30af5743aa0 100644 --- a/homeassistant/components/lupusec/binary_sensor.py +++ b/homeassistant/components/lupusec/binary_sensor.py @@ -4,7 +4,7 @@ import logging import lupupy.constants as CONST -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorDevice +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class LupusecBinarySensor(LupusecDevice, BinarySensorDevice): +class LupusecBinarySensor(LupusecDevice, BinarySensorEntity): """A binary sensor implementation for Lupusec device.""" @property diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index 866c82a7b2a..e2e143da435 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -3,7 +3,7 @@ from pylutron import OccupancyGroup from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, - BinarySensorDevice, + BinarySensorEntity, ) from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class LutronOccupancySensor(LutronDevice, BinarySensorDevice): +class LutronOccupancySensor(LutronDevice, BinarySensorEntity): """Representation of a Lutron Occupancy Group. The Lutron integration API reports "occupancy groups" rather than diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index 871f3c28664..15c3d19008a 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -3,7 +3,7 @@ from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED from homeassistant.components.binary_sensor import ( DEVICE_CLASS_OCCUPANCY, - BinarySensorDevice, + BinarySensorEntity, ) from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice @@ -21,7 +21,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities, True) -class LutronOccupancySensor(LutronCasetaDevice, BinarySensorDevice): +class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): """Representation of a Lutron occupancy group.""" @property diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index 2670b61b456..b42c96f99c2 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -1,7 +1,7 @@ """Support for MAX! binary sensors via MAX! Cube.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DATA_KEY @@ -24,11 +24,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class MaxCubeShutter(BinarySensorDevice): +class MaxCubeShutter(BinarySensorEntity): """Representation of a MAX! Cube Binary Sensor device.""" def __init__(self, handler, name, rf_address): - """Initialize MAX! Cube BinarySensorDevice.""" + """Initialize MAX! Cube BinarySensorEntity.""" self._name = name self._sensor_type = "window" self._rf_address = rf_address @@ -42,7 +42,7 @@ class MaxCubeShutter(BinarySensorDevice): @property def name(self): - """Return the name of the BinarySensorDevice.""" + """Return the name of the BinarySensorEntity.""" return self._name @property diff --git a/homeassistant/components/mcp23017/binary_sensor.py b/homeassistant/components/mcp23017/binary_sensor.py index 59f268e657c..8cc50fa9dfa 100644 --- a/homeassistant/components/mcp23017/binary_sensor.py +++ b/homeassistant/components/mcp23017/binary_sensor.py @@ -7,7 +7,7 @@ import busio # pylint: disable=import-error import digitalio # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -60,7 +60,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices(binary_sensors, True) -class MCP23017BinarySensor(BinarySensorDevice): +class MCP23017BinarySensor(BinarySensorEntity): """Represent a binary sensor that uses MCP23017.""" def __init__(self, name, pin, pull_mode, invert_logic): diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index 7d3bea4c995..ebdeaa7c903 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -5,7 +5,7 @@ import logging from meteoalertapi import Meteoalert import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -49,7 +49,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([MeteoAlertBinarySensor(api, name)], True) -class MeteoAlertBinarySensor(BinarySensorDevice): +class MeteoAlertBinarySensor(BinarySensorEntity): """Representation of a MeteoAlert binary sensor.""" def __init__(self, api, name): diff --git a/homeassistant/components/minecraft_server/binary_sensor.py b/homeassistant/components/minecraft_server/binary_sensor.py index cde2a414900..aadcba44e85 100644 --- a/homeassistant/components/minecraft_server/binary_sensor.py +++ b/homeassistant/components/minecraft_server/binary_sensor.py @@ -2,7 +2,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -24,7 +24,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class MinecraftServerStatusBinarySensor(MinecraftServerEntity, BinarySensorDevice): +class MinecraftServerStatusBinarySensor(MinecraftServerEntity, BinarySensorEntity): """Representation of a Minecraft Server status binary sensor.""" def __init__(self, server: MinecraftServer) -> None: diff --git a/homeassistant/components/mobile_app/binary_sensor.py b/homeassistant/components/mobile_app/binary_sensor.py index c04a1af316d..ae8efc0c113 100644 --- a/homeassistant/components/mobile_app/binary_sensor.py +++ b/homeassistant/components/mobile_app/binary_sensor.py @@ -1,7 +1,7 @@ """Binary sensor platform for mobile_app.""" from functools import partial -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -57,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MobileAppBinarySensor(MobileAppEntity, BinarySensorDevice): +class MobileAppBinarySensor(MobileAppEntity, BinarySensorEntity): """Representation of an mobile app binary sensor.""" @property diff --git a/homeassistant/components/modbus/binary_sensor.py b/homeassistant/components/modbus/binary_sensor.py index 5f80813d108..ce66b7aecdb 100644 --- a/homeassistant/components/modbus/binary_sensor.py +++ b/homeassistant/components/modbus/binary_sensor.py @@ -9,7 +9,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, CONF_SLAVE from homeassistant.helpers import config_validation as cv @@ -73,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class ModbusBinarySensor(BinarySensorDevice): +class ModbusBinarySensor(BinarySensorEntity): """Modbus binary sensor.""" def __init__(self, hub, name, slave, address, device_class, input_type): diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index f07be49b421..4e335dda959 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components import binary_sensor, mqtt from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( CONF_DEVICE, @@ -107,7 +107,7 @@ class MqttBinarySensor( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - BinarySensorDevice, + BinarySensorEntity, ): """Representation a binary sensor that is updated by MQTT.""" diff --git a/homeassistant/components/mychevy/binary_sensor.py b/homeassistant/components/mychevy/binary_sensor.py index 702f3146f8e..b009fe3b3f4 100644 --- a/homeassistant/components/mychevy/binary_sensor.py +++ b/homeassistant/components/mychevy/binary_sensor.py @@ -3,7 +3,7 @@ import logging from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.core import callback from homeassistant.util import slugify @@ -29,7 +29,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class EVBinarySensor(BinarySensorDevice): +class EVBinarySensor(BinarySensorEntity): """Base EVSensor class. The only real difference between sensors is which units and what diff --git a/homeassistant/components/myq/binary_sensor.py b/homeassistant/components/myq/binary_sensor.py index a54b8e50ece..8da5b2083e3 100644 --- a/homeassistant/components/myq/binary_sensor.py +++ b/homeassistant/components/myq/binary_sensor.py @@ -3,7 +3,7 @@ import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, - BinarySensorDevice, + BinarySensorEntity, ) from .const import ( @@ -31,12 +31,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): for device in myq.devices.values(): if device.device_json[MYQ_DEVICE_FAMILY] == MYQ_DEVICE_FAMILY_GATEWAY: - entities.append(MyQBinarySensorDevice(coordinator, device)) + entities.append(MyQBinarySensorEntity(coordinator, device)) async_add_entities(entities, True) -class MyQBinarySensorDevice(BinarySensorDevice): +class MyQBinarySensorEntity(BinarySensorEntity): """Representation of a MyQ gateway.""" def __init__(self, coordinator, device): diff --git a/homeassistant/components/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index 0b94e764937..0bab6ea6eea 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -3,7 +3,7 @@ from homeassistant.components import mysensors from homeassistant.components.binary_sensor import ( DEVICE_CLASSES, DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import STATE_ON @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MySensorsBinarySensor(mysensors.device.MySensorsEntity, BinarySensorDevice): +class MySensorsBinarySensor(mysensors.device.MySensorsEntity, BinarySensorEntity): """Representation of a MySensors Binary Sensor child node.""" @property diff --git a/homeassistant/components/mystrom/binary_sensor.py b/homeassistant/components/mystrom/binary_sensor.py index c584440874a..87c1a3a2665 100644 --- a/homeassistant/components/mystrom/binary_sensor.py +++ b/homeassistant/components/mystrom/binary_sensor.py @@ -1,7 +1,7 @@ """Support for the myStrom buttons.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.components.http import HomeAssistantView from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY from homeassistant.core import callback @@ -59,7 +59,7 @@ class MyStromView(HomeAssistantView): self.buttons[entity_id].async_on_update(new_state) -class MyStromBinarySensor(BinarySensorDevice): +class MyStromBinarySensor(BinarySensorEntity): """Representation of a myStrom button.""" def __init__(self, button_id): diff --git a/homeassistant/components/ness_alarm/binary_sensor.py b/homeassistant/components/ness_alarm/binary_sensor.py index c719febdb58..d7e7851792e 100644 --- a/homeassistant/components/ness_alarm/binary_sensor.py +++ b/homeassistant/components/ness_alarm/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Ness D8X/D16X zone states - represented as binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -38,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class NessZoneBinarySensor(BinarySensorDevice): +class NessZoneBinarySensor(BinarySensorEntity): """Representation of an Ness alarm zone as a binary sensor.""" def __init__(self, zone_id, name, zone_type): diff --git a/homeassistant/components/nest/binary_sensor.py b/homeassistant/components/nest/binary_sensor.py index 34dc7b06ade..dd52e1d665f 100644 --- a/homeassistant/components/nest/binary_sensor.py +++ b/homeassistant/components/nest/binary_sensor.py @@ -2,7 +2,7 @@ from itertools import chain import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS from . import CONF_BINARY_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice @@ -114,7 +114,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(await hass.async_add_job(get_binary_sensors), True) -class NestBinarySensor(NestSensorDevice, BinarySensorDevice): +class NestBinarySensor(NestSensorDevice, BinarySensorEntity): """Represents a Nest binary sensor.""" @property diff --git a/homeassistant/components/netgear_lte/binary_sensor.py b/homeassistant/components/netgear_lte/binary_sensor.py index 31eb46925a1..f18c7a2ff86 100644 --- a/homeassistant/components/netgear_lte/binary_sensor.py +++ b/homeassistant/components/netgear_lte/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Netgear LTE binary sensors.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.exceptions import PlatformNotReady from . import CONF_MONITORED_CONDITIONS, DATA_KEY, LTEEntity @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info) async_add_entities(binary_sensors) -class LTEBinarySensor(LTEEntity, BinarySensorDevice): +class LTEBinarySensor(LTEEntity, BinarySensorEntity): """Netgear LTE binary sensor entity.""" @property diff --git a/homeassistant/components/nexia/binary_sensor.py b/homeassistant/components/nexia/binary_sensor.py index 5c33412c647..9adf47ee2c5 100644 --- a/homeassistant/components/nexia/binary_sensor.py +++ b/homeassistant/components/nexia/binary_sensor.py @@ -1,6 +1,6 @@ """Support for Nexia / Trane XL Thermostats.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from .const import DOMAIN, NEXIA_DEVICE, UPDATE_COORDINATOR from .entity import NexiaThermostatEntity @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NexiaBinarySensor(NexiaThermostatEntity, BinarySensorDevice): +class NexiaBinarySensor(NexiaThermostatEntity, BinarySensorEntity): """Provices Nexia BinarySensor support.""" def __init__(self, coordinator, thermostat, sensor_call, sensor_name): diff --git a/homeassistant/components/nextcloud/binary_sensor.py b/homeassistant/components/nextcloud/binary_sensor.py index 9e4c6f5d969..67bc580bfdf 100644 --- a/homeassistant/components/nextcloud/binary_sensor.py +++ b/homeassistant/components/nextcloud/binary_sensor.py @@ -1,7 +1,7 @@ """Summary binary data from Nextcoud.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import BINARY_SENSORS, DOMAIN @@ -19,7 +19,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors, True) -class NextcloudBinarySensor(BinarySensorDevice): +class NextcloudBinarySensor(BinarySensorEntity): """Represents a Nextcloud binary sensor.""" def __init__(self, item): diff --git a/homeassistant/components/nissan_leaf/binary_sensor.py b/homeassistant/components/nissan_leaf/binary_sensor.py index 786d495cc9b..3d2064dad4c 100644 --- a/homeassistant/components/nissan_leaf/binary_sensor.py +++ b/homeassistant/components/nissan_leaf/binary_sensor.py @@ -1,7 +1,7 @@ """Plugged In Status Support for the Nissan Leaf.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DATA_CHARGING, DATA_LEAF, DATA_PLUGGED_IN, LeafEntity @@ -22,7 +22,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class LeafPluggedInSensor(LeafEntity, BinarySensorDevice): +class LeafPluggedInSensor(LeafEntity, BinarySensorEntity): """Plugged In Sensor class.""" @property @@ -43,7 +43,7 @@ class LeafPluggedInSensor(LeafEntity, BinarySensorDevice): return "mdi:power-plug-off" -class LeafChargingSensor(LeafEntity, BinarySensorDevice): +class LeafChargingSensor(LeafEntity, BinarySensorEntity): """Charging Sensor class.""" @property diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index 53a98204704..8d60ef0901a 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Notion binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from . import ( @@ -50,7 +50,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensor_list, True) -class NotionBinarySensor(NotionEntity, BinarySensorDevice): +class NotionBinarySensor(NotionEntity, BinarySensorEntity): """Define a Notion sensor.""" @property diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index f6006ff2de4..d12f337c171 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -72,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class NX584ZoneSensor(BinarySensorDevice): +class NX584ZoneSensor(BinarySensorEntity): """Representation of a NX584 zone as a sensor.""" def __init__(self, zone, zone_type): diff --git a/homeassistant/components/octoprint/binary_sensor.py b/homeassistant/components/octoprint/binary_sensor.py index 7ed1170c6a0..0f740525f84 100644 --- a/homeassistant/components/octoprint/binary_sensor.py +++ b/homeassistant/components/octoprint/binary_sensor.py @@ -3,7 +3,7 @@ import logging import requests -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import BINARY_SENSOR_TYPES, DOMAIN as COMPONENT_DOMAIN @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class OctoPrintBinarySensor(BinarySensorDevice): +class OctoPrintBinarySensor(BinarySensorEntity): """Representation an OctoPrint binary sensor.""" def __init__( diff --git a/homeassistant/components/opentherm_gw/binary_sensor.py b/homeassistant/components/opentherm_gw/binary_sensor.py index 62c0d3dd2c1..9e3c4d41229 100644 --- a/homeassistant/components/opentherm_gw/binary_sensor.py +++ b/homeassistant/components/opentherm_gw/binary_sensor.py @@ -1,7 +1,7 @@ """Support for OpenTherm Gateway binary sensors.""" import logging -from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorDevice +from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity from homeassistant.const import CONF_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class OpenThermBinarySensor(BinarySensorDevice): +class OpenThermBinarySensor(BinarySensorEntity): """Represent an OpenTherm Gateway binary sensor.""" def __init__(self, gw_dev, var, device_class, friendly_name_format): diff --git a/homeassistant/components/openuv/binary_sensor.py b/homeassistant/components/openuv/binary_sensor.py index 6e403a59b43..2d514b33cf3 100644 --- a/homeassistant/components/openuv/binary_sensor.py +++ b/homeassistant/components/openuv/binary_sensor.py @@ -1,7 +1,7 @@ """Support for OpenUV binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.util.dt import as_local, parse_datetime, utcnow @@ -37,7 +37,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(binary_sensors, True) -class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice): +class OpenUvBinarySensor(OpenUvEntity, BinarySensorEntity): """Define a binary sensor for OpenUV.""" def __init__(self, openuv, sensor_type, name, icon, entry_id): diff --git a/homeassistant/components/orangepi_gpio/binary_sensor.py b/homeassistant/components/orangepi_gpio/binary_sensor.py index b89442a571c..0c8f8bc69cf 100644 --- a/homeassistant/components/orangepi_gpio/binary_sensor.py +++ b/homeassistant/components/orangepi_gpio/binary_sensor.py @@ -1,6 +1,6 @@ """Support for binary sensor using Orange Pi GPIO.""" -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from . import edge_detect, read_input, setup_input, setup_mode from .const import CONF_INVERT_LOGIC, CONF_PIN_MODE, CONF_PORTS, PORT_SCHEMA @@ -24,7 +24,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(binary_sensors) -class OPiGPIOBinarySensor(BinarySensorDevice): +class OPiGPIOBinarySensor(BinarySensorEntity): """Represent a binary sensor that uses Orange Pi GPIO.""" def __init__(self, hass, name, port, invert_logic): diff --git a/homeassistant/components/pcal9535a/binary_sensor.py b/homeassistant/components/pcal9535a/binary_sensor.py index 236fd47af73..8e14ea8ce69 100644 --- a/homeassistant/components/pcal9535a/binary_sensor.py +++ b/homeassistant/components/pcal9535a/binary_sensor.py @@ -4,7 +4,7 @@ import logging from pcal9535a import PCAL9535A import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors, True) -class PCAL9535ABinarySensor(BinarySensorDevice): +class PCAL9535ABinarySensor(BinarySensorEntity): """Represent a binary sensor that uses PCAL9535A.""" def __init__(self, name, pin, pull_mode, invert_logic): diff --git a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py index 89f293d1e0d..4eb90101710 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py +++ b/homeassistant/components/pi4ioe5v9xxxx/binary_sensor.py @@ -4,7 +4,7 @@ import logging from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors, True) -class Pi4ioe5v9BinarySensor(BinarySensorDevice): +class Pi4ioe5v9BinarySensor(BinarySensorEntity): """Represent a binary sensor that uses pi4ioe5v9xxxx IO expander in read mode.""" def __init__(self, name, pin, invert_logic): diff --git a/homeassistant/components/pilight/binary_sensor.py b/homeassistant/components/pilight/binary_sensor.py index ae6d562725d..a53e575b875 100644 --- a/homeassistant/components/pilight/binary_sensor.py +++ b/homeassistant/components/pilight/binary_sensor.py @@ -5,7 +5,7 @@ import logging import voluptuous as vol from homeassistant.components import pilight -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( CONF_DISARM_AFTER_TRIGGER, CONF_NAME, @@ -72,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class PilightBinarySensor(BinarySensorDevice): +class PilightBinarySensor(BinarySensorEntity): """Representation of a binary sensor that can be updated using Pilight.""" def __init__(self, hass, name, variable, payload, on_value, off_value): @@ -121,7 +121,7 @@ class PilightBinarySensor(BinarySensorDevice): self.schedule_update_ha_state() -class PilightTriggerSensor(BinarySensorDevice): +class PilightTriggerSensor(BinarySensorEntity): """Representation of a binary sensor that can be updated using Pilight.""" def __init__( diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index 4d9a99c678e..a9c69f4ddad 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -7,7 +7,7 @@ import sys import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_HOST, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([PingBinarySensor(name, PingData(host, count))], True) -class PingBinarySensor(BinarySensorDevice): +class PingBinarySensor(BinarySensorEntity): """Representation of a Ping Binary sensor.""" def __init__(self, name, ping): diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index b417c128913..5a780c2e57a 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Minut Point binary sensors.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -63,7 +63,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice): +class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity): """The platform class required by Home Assistant.""" def __init__(self, point_client, device_id, device_class): diff --git a/homeassistant/components/powerwall/binary_sensor.py b/homeassistant/components/powerwall/binary_sensor.py index 877efdd68fa..160a62a2029 100644 --- a/homeassistant/components/powerwall/binary_sensor.py +++ b/homeassistant/components/powerwall/binary_sensor.py @@ -6,7 +6,7 @@ from tesla_powerwall import GridStatus from homeassistant.components.binary_sensor import ( DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_CONNECTIVITY, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import DEVICE_CLASS_POWER @@ -56,7 +56,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class PowerWallRunningSensor(PowerWallEntity, BinarySensorDevice): +class PowerWallRunningSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall running sensor.""" @property @@ -89,7 +89,7 @@ class PowerWallRunningSensor(PowerWallEntity, BinarySensorDevice): } -class PowerWallConnectedSensor(PowerWallEntity, BinarySensorDevice): +class PowerWallConnectedSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall connected sensor.""" @property @@ -113,7 +113,7 @@ class PowerWallConnectedSensor(PowerWallEntity, BinarySensorDevice): return self._coordinator.data[POWERWALL_API_SITEMASTER].connected_to_tesla -class PowerWallGridStatusSensor(PowerWallEntity, BinarySensorDevice): +class PowerWallGridStatusSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall grid status sensor.""" @property @@ -137,7 +137,7 @@ class PowerWallGridStatusSensor(PowerWallEntity, BinarySensorDevice): return self._coordinator.data[POWERWALL_API_GRID_STATUS] == GridStatus.CONNECTED -class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorDevice): +class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorEntity): """Representation of an Powerwall charging status sensor.""" @property diff --git a/homeassistant/components/proxmoxve/binary_sensor.py b/homeassistant/components/proxmoxve/binary_sensor.py index 15b1f1483e1..698a2c35ae1 100644 --- a/homeassistant/components/proxmoxve/binary_sensor.py +++ b/homeassistant/components/proxmoxve/binary_sensor.py @@ -1,7 +1,7 @@ """Binary sensor to read Proxmox VE data.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_HOST, CONF_PORT from . import CONF_CONTAINERS, CONF_NODES, CONF_VMS, PROXMOX_CLIENTS, ProxmoxItemType @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class ProxmoxBinarySensor(BinarySensorDevice): +class ProxmoxBinarySensor(BinarySensorEntity): """A binary sensor for reading Proxmox VE data.""" def __init__(self, proxmox_client, item_node, item_type, item_id): diff --git a/homeassistant/components/qwikswitch/binary_sensor.py b/homeassistant/components/qwikswitch/binary_sensor.py index b3635dcb1f4..7a416843861 100644 --- a/homeassistant/components/qwikswitch/binary_sensor.py +++ b/homeassistant/components/qwikswitch/binary_sensor.py @@ -3,7 +3,7 @@ import logging from pyqwikswitch.qwikswitch import SENSORS -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from . import DOMAIN as QWIKSWITCH, QSEntity @@ -22,7 +22,7 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): add_entities(devs) -class QSBinarySensor(QSEntity, BinarySensorDevice): +class QSBinarySensor(QSEntity, BinarySensorEntity): """Sensor based on a Qwikswitch relay/dimmer module.""" _val = False diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index c3161cae6ab..4976714f0a2 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -4,7 +4,7 @@ import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -38,7 +38,7 @@ def _create_entities(hass, config_entry): return entities -class RachioControllerBinarySensor(RachioDevice, BinarySensorDevice): +class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity): """Represent a binary sensor that reflects a Rachio state.""" def __init__(self, controller, poll=True): diff --git a/homeassistant/components/rainbird/binary_sensor.py b/homeassistant/components/rainbird/binary_sensor.py index 51c5f7a9dbe..62c6824f5e0 100644 --- a/homeassistant/components/rainbird/binary_sensor.py +++ b/homeassistant/components/rainbird/binary_sensor.py @@ -3,7 +3,7 @@ import logging from pyrainbird import RainbirdController -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import ( DATA_RAINBIRD, @@ -27,7 +27,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class RainBirdSensor(BinarySensorDevice): +class RainBirdSensor(BinarySensorEntity): """A sensor implementation for Rain Bird device.""" def __init__(self, controller: RainbirdController, sensor_type): diff --git a/homeassistant/components/raincloud/binary_sensor.py b/homeassistant/components/raincloud/binary_sensor.py index 2074a57df98..d2659e133b0 100644 --- a/homeassistant/components/raincloud/binary_sensor.py +++ b/homeassistant/components/raincloud/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice): +class RainCloudBinarySensor(RainCloudEntity, BinarySensorEntity): """A sensor implementation for raincloud device.""" @property diff --git a/homeassistant/components/rainmachine/binary_sensor.py b/homeassistant/components/rainmachine/binary_sensor.py index e5fdc8d6b46..b3f7ffb7d6b 100644 --- a/homeassistant/components/rainmachine/binary_sensor.py +++ b/homeassistant/components/rainmachine/binary_sensor.py @@ -1,7 +1,7 @@ """This platform provides binary sensors for key RainMachine data.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -86,7 +86,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice): +class RainMachineBinarySensor(RainMachineEntity, BinarySensorEntity): """A sensor implementation for raincloud device.""" def __init__( diff --git a/homeassistant/components/random/binary_sensor.py b/homeassistant/components/random/binary_sensor.py index e502439b28c..baec29c5937 100644 --- a/homeassistant/components/random/binary_sensor.py +++ b/homeassistant/components/random/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -32,7 +32,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([RandomSensor(name, device_class)], True) -class RandomSensor(BinarySensorDevice): +class RandomSensor(BinarySensorEntity): """Representation of a Random binary sensor.""" def __init__(self, name, device_class): diff --git a/homeassistant/components/raspihats/binary_sensor.py b/homeassistant/components/raspihats/binary_sensor.py index 6a88318706d..ea3130da4ba 100644 --- a/homeassistant/components/raspihats/binary_sensor.py +++ b/homeassistant/components/raspihats/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( CONF_ADDRESS, CONF_DEVICE_CLASS, @@ -81,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors) -class I2CHatBinarySensor(BinarySensorDevice): +class I2CHatBinarySensor(BinarySensorEntity): """Representation of a binary sensor that uses a I2C-HAT digital input.""" I2C_HATS_MANAGER = None diff --git a/homeassistant/components/remote_rpi_gpio/binary_sensor.py b/homeassistant/components/remote_rpi_gpio/binary_sensor.py index 862bd30ae43..d97183de86e 100644 --- a/homeassistant/components/remote_rpi_gpio/binary_sensor.py +++ b/homeassistant/components/remote_rpi_gpio/binary_sensor.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv @@ -57,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class RemoteRPiGPIOBinarySensor(BinarySensorDevice): +class RemoteRPiGPIOBinarySensor(BinarySensorEntity): """Represent a binary sensor that uses a Remote Raspberry Pi GPIO.""" def __init__(self, name, button, invert_logic): diff --git a/homeassistant/components/rest/binary_sensor.py b/homeassistant/components/rest/binary_sensor.py index 1bbde240789..6d797dfd834 100644 --- a/homeassistant/components/rest/binary_sensor.py +++ b/homeassistant/components/rest/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( CONF_AUTHENTICATION, @@ -91,7 +91,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([RestBinarySensor(hass, rest, name, device_class, value_template)]) -class RestBinarySensor(BinarySensorDevice): +class RestBinarySensor(BinarySensorEntity): """Representation of a REST binary sensor.""" def __init__(self, hass, rest, name, device_class, value_template): diff --git a/homeassistant/components/rflink/binary_sensor.py b/homeassistant/components/rflink/binary_sensor.py index c3600da4051..8962af3c0d4 100644 --- a/homeassistant/components/rflink/binary_sensor.py +++ b/homeassistant/components/rflink/binary_sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_FORCE_UPDATE, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -56,7 +56,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices_from_config(config)) -class RflinkBinarySensor(RflinkDevice, BinarySensorDevice): +class RflinkBinarySensor(RflinkDevice, BinarySensorEntity): """Representation of an Rflink binary sensor.""" def __init__( diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index a88594dccea..5e610128ea6 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( CONF_COMMAND_OFF, @@ -171,7 +171,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update) -class RfxtrxBinarySensor(BinarySensorDevice): +class RfxtrxBinarySensor(BinarySensorEntity): """A representation of a RFXtrx binary sensor.""" def __init__( diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index 385d8a4f955..fa303b94378 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -2,7 +2,7 @@ from datetime import datetime import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from . import DOMAIN @@ -37,7 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(sensors) -class RingBinarySensor(RingEntityMixin, BinarySensorDevice): +class RingBinarySensor(RingEntityMixin, BinarySensorEntity): """A binary sensor implementation for Ring device.""" _active_alert = None diff --git a/homeassistant/components/roomba/binary_sensor.py b/homeassistant/components/roomba/binary_sensor.py index 47212563b5b..fb11a0f9d4c 100644 --- a/homeassistant/components/roomba/binary_sensor.py +++ b/homeassistant/components/roomba/binary_sensor.py @@ -1,7 +1,7 @@ """Roomba binary sensor entities.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import roomba_reported_state from .const import BLID, DOMAIN, ROOMBA_SESSION @@ -21,7 +21,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([roomba_vac], True) -class RoombaBinStatus(IRobotEntity, BinarySensorDevice): +class RoombaBinStatus(IRobotEntity, BinarySensorEntity): """Class to hold Roomba Sensor basic info.""" ICON = "mdi:delete-variant" diff --git a/homeassistant/components/rpi_gpio/binary_sensor.py b/homeassistant/components/rpi_gpio/binary_sensor.py index 3e38da47eed..a7ecaa3d36c 100644 --- a/homeassistant/components/rpi_gpio/binary_sensor.py +++ b/homeassistant/components/rpi_gpio/binary_sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import rpi_gpio -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors, True) -class RPiGPIOBinarySensor(BinarySensorDevice): +class RPiGPIOBinarySensor(BinarySensorEntity): """Represent a binary sensor that uses Raspberry Pi GPIO.""" def __init__(self, name, port, pull_mode, bouncetime, invert_logic): diff --git a/homeassistant/components/rpi_pfio/binary_sensor.py b/homeassistant/components/rpi_pfio/binary_sensor.py index 89d44a0e8db..e77ceea3eb7 100644 --- a/homeassistant/components/rpi_pfio/binary_sensor.py +++ b/homeassistant/components/rpi_pfio/binary_sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import rpi_pfio -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): rpi_pfio.activate_listener(hass) -class RPiPFIOBinarySensor(BinarySensorDevice): +class RPiPFIOBinarySensor(BinarySensorEntity): """Represent a binary sensor that a PiFace Digital Input.""" def __init__(self, hass, port, name, settle_time, invert_logic): diff --git a/homeassistant/components/satel_integra/binary_sensor.py b/homeassistant/components/satel_integra/binary_sensor.py index 4a9be339a1c..19763903f27 100644 --- a/homeassistant/components/satel_integra/binary_sensor.py +++ b/homeassistant/components/satel_integra/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Satel Integra zone states- represented as binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -49,7 +49,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class SatelIntegraBinarySensor(BinarySensorDevice): +class SatelIntegraBinarySensor(BinarySensorEntity): """Representation of an Satel Integra binary sensor.""" def __init__( diff --git a/homeassistant/components/sense/binary_sensor.py b/homeassistant/components/sense/binary_sensor.py index 384bc3d074f..62fe0d01d4e 100644 --- a/homeassistant/components/sense/binary_sensor.py +++ b/homeassistant/components/sense/binary_sensor.py @@ -1,7 +1,7 @@ """Support for monitoring a Sense energy sensor device.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, DEVICE_CLASS_POWER from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -61,7 +61,7 @@ def sense_to_mdi(sense_icon): return "mdi:{}".format(MDI_ICONS.get(sense_icon, "power-plug")) -class SenseDevice(BinarySensorDevice): +class SenseDevice(BinarySensorEntity): """Implementation of a Sense energy device binary sensor.""" def __init__(self, sense_devices_data, device, sense_monitor_id): diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index f0df663eba3..a5c6681eb2b 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS 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 SkybellBinarySensor(SkybellDevice, BinarySensorDevice): +class SkybellBinarySensor(SkybellDevice, BinarySensorEntity): """A binary sensor implementation for Skybell devices.""" def __init__(self, device, sensor_type): diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index 3ba39a38764..39ae3e7c658 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,5 +1,5 @@ """Support for SleepIQ sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import SleepIQSensor from .const import DOMAIN, IS_IN_BED, SENSOR_TYPES, SIDES @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class IsInBedBinarySensor(SleepIQSensor, BinarySensorDevice): +class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): """Implementation of a SleepIQ presence sensor.""" def __init__(self, sleepiq_data, bed_id, side): diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 78d2c73ca73..825cf149952 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -3,7 +3,7 @@ from typing import Optional, Sequence from pysmartthings import Attribute, Capability -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -50,7 +50,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: ] -class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorDevice): +class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity): """Define a SmartThings Binary Sensor.""" def __init__(self, device, attribute): diff --git a/homeassistant/components/smarty/binary_sensor.py b/homeassistant/components/smarty/binary_sensor.py index a86b3548e95..f8b9114ae0e 100644 --- a/homeassistant/components/smarty/binary_sensor.py +++ b/homeassistant/components/smarty/binary_sensor.py @@ -2,7 +2,7 @@ import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors, True) -class SmartyBinarySensor(BinarySensorDevice): +class SmartyBinarySensor(BinarySensorEntity): """Representation of a Smarty Binary Sensor.""" def __init__(self, name, device_class, smarty): diff --git a/homeassistant/components/spc/binary_sensor.py b/homeassistant/components/spc/binary_sensor.py index 626e30849df..75256b60cfb 100644 --- a/homeassistant/components/spc/binary_sensor.py +++ b/homeassistant/components/spc/binary_sensor.py @@ -3,7 +3,7 @@ import logging from pyspcwebgw.const import ZoneInput, ZoneType -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -35,7 +35,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SpcBinarySensor(BinarySensorDevice): +class SpcBinarySensor(BinarySensorEntity): """Representation of a sensor based on a SPC zone.""" def __init__(self, zone): diff --git a/homeassistant/components/starline/binary_sensor.py b/homeassistant/components/starline/binary_sensor.py index f2288e9363b..df9bd348b8d 100644 --- a/homeassistant/components/starline/binary_sensor.py +++ b/homeassistant/components/starline/binary_sensor.py @@ -4,7 +4,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_LOCK, DEVICE_CLASS_POWER, DEVICE_CLASS_PROBLEM, - BinarySensorDevice, + BinarySensorEntity, ) from .account import StarlineAccount, StarlineDevice @@ -33,7 +33,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class StarlineSensor(StarlineEntity, BinarySensorDevice): +class StarlineSensor(StarlineEntity, BinarySensorEntity): """Representation of a StarLine binary sensor.""" def __init__( diff --git a/homeassistant/components/stookalert/binary_sensor.py b/homeassistant/components/stookalert/binary_sensor.py index c8515f401da..a07f208ac9d 100644 --- a/homeassistant/components/stookalert/binary_sensor.py +++ b/homeassistant/components/stookalert/binary_sensor.py @@ -5,7 +5,7 @@ import logging import stookalert import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.helpers import config_validation as cv @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([StookalertBinarySensor(name, api_handler)], update_before_add=True) -class StookalertBinarySensor(BinarySensorDevice): +class StookalertBinarySensor(BinarySensorEntity): """An implementation of RIVM Stookalert.""" def __init__(self, name, api_handler): diff --git a/homeassistant/components/streamlabswater/binary_sensor.py b/homeassistant/components/streamlabswater/binary_sensor.py index 78b2ceb4044..a25f5e124e6 100644 --- a/homeassistant/components/streamlabswater/binary_sensor.py +++ b/homeassistant/components/streamlabswater/binary_sensor.py @@ -2,7 +2,7 @@ from datetime import timedelta -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.streamlabswater import DOMAIN as STREAMLABSWATER_DOMAIN from homeassistant.util import Throttle @@ -46,7 +46,7 @@ class StreamlabsLocationData: return self._is_away -class StreamlabsAwayMode(BinarySensorDevice): +class StreamlabsAwayMode(BinarySensorEntity): """Monitor the away mode state.""" def __init__(self, location_name, streamlabs_location_data): diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 5b3ac492137..26f498d43fe 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -8,7 +8,7 @@ from surepy import SureLocationID, SureProductID from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, DEVICE_CLASS_PRESENCE, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_ID, CONF_TYPE from homeassistant.core import callback @@ -55,7 +55,7 @@ async def async_setup_platform( async_add_entities(entities, True) -class SurePetcareBinarySensor(BinarySensorDevice): +class SurePetcareBinarySensor(BinarySensorEntity): """A binary sensor implementation for Sure Petcare Entities.""" def __init__( @@ -105,7 +105,7 @@ class SurePetcareBinarySensor(BinarySensorDevice): return None if not self._device_class else self._device_class @property - def unique_id(self: BinarySensorDevice) -> str: + def unique_id(self: BinarySensorEntity) -> str: """Return an unique ID.""" return f"{self._spc_data['household_id']}-{self._id}" @@ -214,7 +214,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): return f"{self._name}_connectivity" @property - def unique_id(self: BinarySensorDevice) -> str: + def unique_id(self: BinarySensorEntity) -> str: """Return an unique ID.""" return f"{self._spc_data['household_id']}-{self._id}-connectivity" diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index 7621a542838..39e492601bd 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -24,7 +24,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): +class TahomaBinarySensor(TahomaDevice, BinarySensorEntity): """Representation of a Tahoma Binary Sensor.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/tapsaff/binary_sensor.py b/homeassistant/components/tapsaff/binary_sensor.py index e54bc7298b0..912dd27c887 100644 --- a/homeassistant/components/tapsaff/binary_sensor.py +++ b/homeassistant/components/tapsaff/binary_sensor.py @@ -5,7 +5,7 @@ import logging from tapsaff import TapsAff import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TapsAffSensor(taps_aff_data, name)], True) -class TapsAffSensor(BinarySensorDevice): +class TapsAffSensor(BinarySensorEntity): """Implementation of a Taps Aff binary sensor.""" def __init__(self, taps_aff_data, name): diff --git a/homeassistant/components/tcp/binary_sensor.py b/homeassistant/components/tcp/binary_sensor.py index 4d26d819ede..1648ab5e247 100644 --- a/homeassistant/components/tcp/binary_sensor.py +++ b/homeassistant/components/tcp/binary_sensor.py @@ -1,7 +1,7 @@ """Provides a binary sensor which gets its values from a TCP socket.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from .sensor import CONF_VALUE_ON, PLATFORM_SCHEMA, TcpSensor @@ -15,7 +15,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([TcpBinarySensor(hass, config)]) -class TcpBinarySensor(BinarySensorDevice, TcpSensor): +class TcpBinarySensor(BinarySensorEntity, TcpSensor): """A binary sensor which is on when its state == CONF_VALUE_ON.""" required = (CONF_VALUE_ON,) diff --git a/homeassistant/components/tellduslive/binary_sensor.py b/homeassistant/components/tellduslive/binary_sensor.py index 09541a120fd..3f829365689 100644 --- a/homeassistant/components/tellduslive/binary_sensor.py +++ b/homeassistant/components/tellduslive/binary_sensor.py @@ -2,7 +2,7 @@ import logging from homeassistant.components import binary_sensor, tellduslive -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from .entry import TelldusLiveEntity @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TelldusLiveSensor(TelldusLiveEntity, BinarySensorDevice): +class TelldusLiveSensor(TelldusLiveEntity, BinarySensorEntity): """Representation of a Tellstick sensor.""" @property diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index df918d3dd77..94d0f9d597b 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -7,7 +7,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -107,7 +107,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class BinarySensorTemplate(BinarySensorDevice): +class BinarySensorTemplate(BinarySensorEntity): """A virtual binary sensor that triggers from another sensor.""" def __init__( diff --git a/homeassistant/components/tesla/binary_sensor.py b/homeassistant/components/tesla/binary_sensor.py index 8b60cd00163..c1f6fe18b99 100644 --- a/homeassistant/components/tesla/binary_sensor.py +++ b/homeassistant/components/tesla/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Tesla binary sensor.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -26,7 +26,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TeslaBinarySensor(TeslaDevice, BinarySensorDevice): +class TeslaBinarySensor(TeslaDevice, BinarySensorEntity): """Implement an Tesla binary sensor for parking and charger.""" def __init__(self, tesla_device, controller, sensor_type, config_entry): diff --git a/homeassistant/components/threshold/binary_sensor.py b/homeassistant/components/threshold/binary_sensor.py index 509d8cfd4d3..98d426cb660 100644 --- a/homeassistant/components/threshold/binary_sensor.py +++ b/homeassistant/components/threshold/binary_sensor.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -75,7 +75,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class ThresholdSensor(BinarySensorDevice): +class ThresholdSensor(BinarySensorEntity): """Representation of a Threshold sensor.""" def __init__(self, hass, entity_id, name, lower, upper, hysteresis, device_class): diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index ee9969c9974..8a5bbf16c6c 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -5,7 +5,7 @@ import logging import pytz import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ( CONF_AFTER, CONF_BEFORE, @@ -60,7 +60,7 @@ def is_sun_event(event): return event in (SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) -class TodSensor(BinarySensorDevice): +class TodSensor(BinarySensorEntity): """Time of the Day Sensor.""" def __init__(self, name, after, after_offset, before, before_offset): diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index e6ef780ec8e..500cbec1526 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -3,7 +3,7 @@ import logging from typing import Any -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -110,7 +110,7 @@ async def async_setup_entry( async_add_entities(sensors, True) -class ToonBinarySensor(ToonEntity, BinarySensorDevice): +class ToonBinarySensor(ToonEntity, BinarySensorEntity): """Defines an Toon binary sensor.""" def __init__( diff --git a/homeassistant/components/totalconnect/binary_sensor.py b/homeassistant/components/totalconnect/binary_sensor.py index 48d9a96a483..e296b12fa59 100644 --- a/homeassistant/components/totalconnect/binary_sensor.py +++ b/homeassistant/components/totalconnect/binary_sensor.py @@ -5,7 +5,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_DOOR, DEVICE_CLASS_GAS, DEVICE_CLASS_SMOKE, - BinarySensorDevice, + BinarySensorEntity, ) from .const import DOMAIN @@ -26,7 +26,7 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: async_add_entities(sensors, True) -class TotalConnectBinarySensor(BinarySensorDevice): +class TotalConnectBinarySensor(BinarySensorEntity): """Represent an TotalConnect zone.""" def __init__(self, zone_id, location_id, zone): diff --git a/homeassistant/components/trend/binary_sensor.py b/homeassistant/components/trend/binary_sensor.py index 7c4a2dc4067..1490efd7a86 100644 --- a/homeassistant/components/trend/binary_sensor.py +++ b/homeassistant/components/trend/binary_sensor.py @@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -95,7 +95,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class SensorTrend(BinarySensorDevice): +class SensorTrend(BinarySensorEntity): """Representation of a trend Sensor.""" def __init__( diff --git a/homeassistant/components/upcloud/binary_sensor.py b/homeassistant/components/upcloud/binary_sensor.py index 12324599958..2b29aa0e24f 100644 --- a/homeassistant/components/upcloud/binary_sensor.py +++ b/homeassistant/components/upcloud/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity import homeassistant.helpers.config_validation as cv from . import CONF_SERVERS, DATA_UPCLOUD, UpCloudServerEntity @@ -26,5 +26,5 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class UpCloudBinarySensor(UpCloudServerEntity, BinarySensorDevice): +class UpCloudBinarySensor(UpCloudServerEntity, BinarySensorEntity): """Representation of an UpCloud server sensor.""" diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index 7abab616d5c..5088856ce6d 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -1,6 +1,6 @@ """Support for Home Assistant Updater binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import ATTR_NEWEST_VERSION, ATTR_RELEASE_NOTES, DOMAIN as UPDATER_DOMAIN @@ -13,7 +13,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([UpdaterBinary(hass.data[UPDATER_DOMAIN])]) -class UpdaterBinary(BinarySensorDevice): +class UpdaterBinary(BinarySensorEntity): """Representation of an updater binary sensor.""" def __init__(self, coordinator): diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index 401da496d2f..231dce9a402 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -4,7 +4,7 @@ import logging from pyuptimerobot import UptimeRobot import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class UptimeRobotBinarySensor(BinarySensorDevice): +class UptimeRobotBinarySensor(BinarySensorEntity): """Representation of a Uptime Robot binary sensor.""" def __init__(self, api_key, up_robot, monitor_id, name, target): diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 86f4e7a7cd8..8c4cc5a4043 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Velbus Binary Sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import VelbusEntity from .const import DOMAIN @@ -20,7 +20,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusBinarySensor(VelbusEntity, BinarySensorDevice): +class VelbusBinarySensor(VelbusEntity, BinarySensorEntity): """Representation of a Velbus Binary Sensor.""" @property diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 621dc09930d..557874f846a 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -5,7 +5,7 @@ from typing import Callable, List from homeassistant.components.binary_sensor import ( DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -32,7 +32,7 @@ async def async_setup_entry( ) -class VeraBinarySensor(VeraDevice, BinarySensorDevice): +class VeraBinarySensor(VeraDevice, BinarySensorEntity): """Representation of a Vera Binary Sensor.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/verisure/binary_sensor.py b/homeassistant/components/verisure/binary_sensor.py index bbdd9f54e83..206a4072ace 100644 --- a/homeassistant/components/verisure/binary_sensor.py +++ b/homeassistant/components/verisure/binary_sensor.py @@ -3,7 +3,7 @@ import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, - BinarySensorDevice, + BinarySensorEntity, ) from . import CONF_DOOR_WINDOW, HUB as hub @@ -30,7 +30,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors) -class VerisureDoorWindowSensor(BinarySensorDevice): +class VerisureDoorWindowSensor(BinarySensorEntity): """Representation of a Verisure door window sensor.""" def __init__(self, device_label): @@ -73,7 +73,7 @@ class VerisureDoorWindowSensor(BinarySensorDevice): hub.update_overview() -class VerisureEthernetStatus(BinarySensorDevice): +class VerisureEthernetStatus(BinarySensorEntity): """Representation of a Verisure VBOX internet status.""" @property diff --git a/homeassistant/components/volvooncall/binary_sensor.py b/homeassistant/components/volvooncall/binary_sensor.py index 20b07bba940..0dba6186ef9 100644 --- a/homeassistant/components/volvooncall/binary_sensor.py +++ b/homeassistant/components/volvooncall/binary_sensor.py @@ -1,7 +1,7 @@ """Support for VOC.""" import logging -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorDevice +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity from . import DATA_KEY, VolvoEntity @@ -15,7 +15,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, BinarySensorDevice): +class VolvoSensor(VolvoEntity, BinarySensorEntity): """Representation of a Volvo sensor.""" @property diff --git a/homeassistant/components/vultr/binary_sensor.py b/homeassistant/components/vultr/binary_sensor.py index 6b5b9bbd0f8..c1b60479e7a 100644 --- a/homeassistant/components/vultr/binary_sensor.py +++ b/homeassistant/components/vultr/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([VultrBinarySensor(vultr, subscription, name)], True) -class VultrBinarySensor(BinarySensorDevice): +class VultrBinarySensor(BinarySensorEntity): """Representation of a Vultr subscription sensor.""" def __init__(self, vultr, subscription, name): diff --git a/homeassistant/components/w800rf32/binary_sensor.py b/homeassistant/components/w800rf32/binary_sensor.py index 16239e0c98c..35b52a1932d 100644 --- a/homeassistant/components/w800rf32/binary_sensor.py +++ b/homeassistant/components/w800rf32/binary_sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_DEVICES, CONF_NAME from homeassistant.core import callback @@ -64,7 +64,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None): add_entities(binary_sensors) -class W800rf32BinarySensor(BinarySensorDevice): +class W800rf32BinarySensor(BinarySensorEntity): """A representation of a w800rf32 binary sensor.""" def __init__(self, device_id, name, device_class=None, off_delay=None): diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index 0ca1f950448..805a730e3d9 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -4,7 +4,7 @@ import logging import async_timeout -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DOMAIN as WEMO_DOMAIN @@ -29,7 +29,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class WemoBinarySensor(BinarySensorDevice): +class WemoBinarySensor(BinarySensorEntity): """Representation a WeMo binary sensor.""" def __init__(self, device): diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index 6dd22a3f7b8..d8967dd064d 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -3,7 +3,7 @@ import logging import pywink -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN, WinkDevice @@ -33,12 +33,12 @@ 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([WinkBinarySensorDevice(sensor, hass)]) + add_entities([WinkBinarySensorEntity(sensor, hass)]) for key in pywink.get_keys(): _id = key.object_id() + key.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkBinarySensorDevice(key, hass)]) + add_entities([WinkBinarySensorEntity(key, hass)]) for sensor in pywink.get_smoke_and_co_detectors(): _id = sensor.object_id() + sensor.name() @@ -68,19 +68,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for door_bell_sensor in pywink.get_door_bells(): _id = door_bell_sensor.object_id() + door_bell_sensor.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkBinarySensorDevice(door_bell_sensor, hass)]) + add_entities([WinkBinarySensorEntity(door_bell_sensor, hass)]) for camera_sensor in pywink.get_cameras(): _id = camera_sensor.object_id() + camera_sensor.name() if _id not in hass.data[DOMAIN]["unique_ids"]: try: if camera_sensor.capability() in SENSOR_TYPES: - add_entities([WinkBinarySensorDevice(camera_sensor, hass)]) + add_entities([WinkBinarySensorEntity(camera_sensor, hass)]) except AttributeError: _LOGGER.info("Device isn't a sensor, skipping") -class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): +class WinkBinarySensorEntity(WinkDevice, BinarySensorEntity): """Representation of a Wink binary sensor.""" def __init__(self, wink, hass): @@ -115,7 +115,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): return super().device_state_attributes -class WinkSmokeDetector(WinkBinarySensorDevice): +class WinkSmokeDetector(WinkBinarySensorEntity): """Representation of a Wink Smoke detector.""" @property @@ -126,7 +126,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice): return _attributes -class WinkHub(WinkBinarySensorDevice): +class WinkHub(WinkBinarySensorEntity): """Representation of a Wink Hub.""" @property @@ -146,7 +146,7 @@ class WinkHub(WinkBinarySensorDevice): return _attributes -class WinkRemote(WinkBinarySensorDevice): +class WinkRemote(WinkBinarySensorEntity): """Representation of a Wink Lutron Connected bulb remote.""" @property @@ -165,7 +165,7 @@ class WinkRemote(WinkBinarySensorDevice): return None -class WinkButton(WinkBinarySensorDevice): +class WinkButton(WinkBinarySensorEntity): """Representation of a Wink Relay button.""" @property @@ -177,7 +177,7 @@ class WinkButton(WinkBinarySensorDevice): return _attributes -class WinkGang(WinkBinarySensorDevice): +class WinkGang(WinkBinarySensorEntity): """Representation of a Wink Relay gang.""" @property diff --git a/homeassistant/components/wirelesstag/binary_sensor.py b/homeassistant/components/wirelesstag/binary_sensor.py index eae8c17edcd..b92cdeb0bca 100644 --- a/homeassistant/components/wirelesstag/binary_sensor.py +++ b/homeassistant/components/wirelesstag/binary_sensor.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_OFF, STATE_ON from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -88,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.add_job(platform.install_push_notifications, sensors) -class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice): +class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorEntity): """A binary sensor implementation for WirelessTags.""" def __init__(self, api, tag, sensor_type): diff --git a/homeassistant/components/workday/binary_sensor.py b/homeassistant/components/workday/binary_sensor.py index 0aa1f5bfc42..1613f10d66a 100644 --- a/homeassistant/components/workday/binary_sensor.py +++ b/homeassistant/components/workday/binary_sensor.py @@ -6,7 +6,7 @@ from typing import Any import holidays import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity from homeassistant.const import CONF_NAME, WEEKDAYS import homeassistant.helpers.config_validation as cv @@ -119,7 +119,7 @@ def get_date(date): return date -class IsWorkdaySensor(BinarySensorDevice): +class IsWorkdaySensor(BinarySensorEntity): """Implementation of a Workday sensor.""" def __init__(self, obj_holidays, workdays, excludes, days_offset, name): diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index 2c95198f348..01caddb7eb5 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Xiaomi aqara binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.event import async_call_later @@ -99,7 +99,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice): +class XiaomiBinarySensor(XiaomiDevice, BinarySensorEntity): """Representation of a base XiaomiBinarySensor.""" def __init__(self, device, name, xiaomi_hub, data_key, device_class): diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py index f5f3e03b765..78a20ab0104 100644 --- a/homeassistant/components/yeelight/binary_sensor.py +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -1,7 +1,7 @@ """Sensor platform support for yeelight.""" import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import DATA_UPDATED, DATA_YEELIGHT @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([YeelightNightlightModeSensor(device)]) -class YeelightNightlightModeSensor(BinarySensorDevice): +class YeelightNightlightModeSensor(BinarySensorEntity): """Representation of a Yeelight nightlight mode sensor.""" def __init__(self, device): diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 044d32890da..0ed931e92da 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -12,7 +12,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_SMOKE, DEVICE_CLASS_VIBRATION, DOMAIN, - BinarySensorDevice, + BinarySensorEntity, ) from homeassistant.const import STATE_ON from homeassistant.core import callback @@ -61,7 +61,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) -class BinarySensor(ZhaEntity, BinarySensorDevice): +class BinarySensor(ZhaEntity, BinarySensorEntity): """ZHA BinarySensor.""" SENSOR_ATTR = None diff --git a/homeassistant/components/zigbee/binary_sensor.py b/homeassistant/components/zigbee/binary_sensor.py index d32554e5744..fe35b54a88f 100644 --- a/homeassistant/components/zigbee/binary_sensor.py +++ b/homeassistant/components/zigbee/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Zigbee binary sensors.""" import voluptuous as vol -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalIn, ZigBeeDigitalInConfig @@ -21,5 +21,5 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice): +class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorEntity): """Use ZigBeeDigitalIn as binary sensor.""" diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index 16c22cb48e7..739864fdea8 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -1,5 +1,5 @@ """Support for ZoneMinder binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import BinarySensorEntity from . import DOMAIN as ZONEMINDER_DOMAIN @@ -13,7 +13,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None): return True -class ZMAvailabilitySensor(BinarySensorDevice): +class ZMAvailabilitySensor(BinarySensorEntity): """Representation of the availability of ZoneMinder as a binary sensor.""" def __init__(self, host_name, client): diff --git a/homeassistant/components/zwave/binary_sensor.py b/homeassistant/components/zwave/binary_sensor.py index e4bafc44bee..094279c4e7a 100644 --- a/homeassistant/components/zwave/binary_sensor.py +++ b/homeassistant/components/zwave/binary_sensor.py @@ -2,7 +2,7 @@ import datetime import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import track_point_in_time @@ -39,7 +39,7 @@ def get_device(values, **kwargs): return None -class ZWaveBinarySensor(BinarySensorDevice, ZWaveDeviceEntity): +class ZWaveBinarySensor(BinarySensorEntity, ZWaveDeviceEntity): """Representation of a binary sensor within Z-Wave.""" def __init__(self, values, device_class): diff --git a/tests/components/binary_sensor/test_init.py b/tests/components/binary_sensor/test_init.py index 2299ba4c9a2..a40df84b899 100644 --- a/tests/components/binary_sensor/test_init.py +++ b/tests/components/binary_sensor/test_init.py @@ -1,24 +1,29 @@ """The tests for the Binary sensor component.""" -import unittest from unittest import mock from homeassistant.components import binary_sensor from homeassistant.const import STATE_OFF, STATE_ON -class TestBinarySensor(unittest.TestCase): - """Test the binary_sensor base class.""" +def test_state(): + """Test binary sensor state.""" + sensor = binary_sensor.BinarySensorEntity() + assert STATE_OFF == sensor.state + with mock.patch( + "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=False, + ): + assert STATE_OFF == binary_sensor.BinarySensorEntity().state + with mock.patch( + "homeassistant.components.binary_sensor.BinarySensorEntity.is_on", new=True, + ): + assert STATE_ON == binary_sensor.BinarySensorEntity().state - def test_state(self): - """Test binary sensor state.""" - sensor = binary_sensor.BinarySensorDevice() - assert STATE_OFF == sensor.state - with mock.patch( - "homeassistant.components.binary_sensor.BinarySensorDevice.is_on", - new=False, - ): - assert STATE_OFF == binary_sensor.BinarySensorDevice().state - with mock.patch( - "homeassistant.components.binary_sensor.BinarySensorDevice.is_on", new=True, - ): - assert STATE_ON == binary_sensor.BinarySensorDevice().state + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomBinarySensor(binary_sensor.BinarySensorDevice): + pass + + CustomBinarySensor() + assert "BinarySensorDevice is deprecated, modify CustomBinarySensor" in caplog.text diff --git a/tests/testing_config/custom_components/test/binary_sensor.py b/tests/testing_config/custom_components/test/binary_sensor.py index bcff0adb4e4..13a877f7e8c 100644 --- a/tests/testing_config/custom_components/test/binary_sensor.py +++ b/tests/testing_config/custom_components/test/binary_sensor.py @@ -3,7 +3,7 @@ Provide a mock binary sensor platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorDevice +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity from tests.common import MockEntity @@ -36,7 +36,7 @@ async def async_setup_platform( async_add_entities_callback(list(ENTITIES.values())) -class MockBinarySensor(MockEntity, BinarySensorDevice): +class MockBinarySensor(MockEntity, BinarySensorEntity): """Mock Binary Sensor class.""" @property From 5a9ee9bd6c54f2bfb00cdeff79d4b3a0bd4d67ad Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 22:00:28 +0200 Subject: [PATCH 025/511] Upgrade mock-open to 1.4.0 (#34606) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 4949034b6d9..aff13fe4c3b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ -r requirements_test_pre_commit.txt asynctest==0.13.0 codecov==2.0.15 -mock-open==1.3.1 +mock-open==1.4.0 mypy==0.770 pre-commit==2.3.0 pylint==2.4.4 From cc2815025312d436fe972af52d1b68b71ce8492e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 22:13:31 +0200 Subject: [PATCH 026/511] Upgrade pyupgrade to v2.2.1 (#34608) --- .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 e491637ea65..05e062e43b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.1.0 + rev: v2.2.1 hooks: - id: pyupgrade args: [--py37-plus] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index a52927ed497..e64e499baf7 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -7,5 +7,5 @@ flake8-docstrings==1.5.0 flake8==3.7.9 isort==4.3.21 pydocstyle==5.0.2 -pyupgrade==2.1.0 +pyupgrade==2.2.1 yamllint==1.23.0 From 7ff196d043c4a7abe90345e9a554643093f4a04e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 Apr 2020 15:53:31 -0500 Subject: [PATCH 027/511] Restore ability to overwrite homekit max temp bound (#34612) --- homeassistant/components/homekit/type_thermostats.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 8691dc51c05..0a488917381 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -119,9 +119,11 @@ class Thermostat(HomeAccessory): self._unit = self.hass.config.units.temperature_unit min_temp, max_temp = self.get_temperature_range() - # Homekit only supports 10-38 + # Homekit only supports 10-38, overwriting + # the max to appears to work, but less than 10 causes + # a crash on the home app hc_min_temp = max(min_temp, HC_MIN_TEMP) - hc_max_temp = min(max_temp, HC_MAX_TEMP) + hc_max_temp = max_temp min_humidity = self.hass.states.get(self.entity_id).attributes.get( ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY From e9c5f3e74a050121fa65808d8edfe70c700d32bb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 23 Apr 2020 23:05:05 +0200 Subject: [PATCH 028/511] Upgrade codecov to 2.0.22 (#34607) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index aff13fe4c3b..8a9bc7ddf84 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -4,7 +4,7 @@ -r requirements_test_pre_commit.txt asynctest==0.13.0 -codecov==2.0.15 +codecov==2.0.22 mock-open==1.4.0 mypy==0.770 pre-commit==2.3.0 From 7bfc1d284018aae90af4cb74614bfaa58cc4224a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Fri, 24 Apr 2020 00:07:07 +0200 Subject: [PATCH 029/511] Use "arming" state during transition in manual alarm panel (#32950) * Manual Alarm Control Panel: use proper "Arming" state * Update previous and next attributes * add CONF_ARMING_TIME * Split up arming and pending time, pending_time --> arming_time * update tests * fix issort * fix issort * fix demo platform * fix alarm test * remove arming_time from the triggered state * Match previous default "delay_time" * fix tests * fix arming state when triggering * fix arming _arming_time_by_state for Triggering state * change to not in list * Update homeassistant/components/manual/alarm_control_panel.py Co-Authored-By: Martin Hjelmare * async_update_ha_state -> async_write_ha_state * black formatting * add Callback Co-Authored-By: Martin Hjelmare * import callback * update device triggers alarm_control_panel * Update test_device_trigger.py * Update device_trigger.py Co-authored-by: Martin Hjelmare --- .../alarm_control_panel/device_trigger.py | 24 ++- .../components/demo/alarm_control_panel.py | 12 +- .../components/manual/alarm_control_panel.py | 99 ++++++----- homeassistant/const.py | 1 + .../test_device_trigger.py | 7 + .../manual/test_alarm_control_panel.py | 159 +++++++++--------- 6 files changed, 171 insertions(+), 131 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index 95ae17aaaf5..849da062665 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -19,6 +19,7 @@ from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, @@ -32,6 +33,7 @@ from . import DOMAIN TRIGGER_TYPES = { "triggered", "disarmed", + "arming", "armed_home", "armed_away", "armed_night", @@ -79,6 +81,13 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "triggered", }, + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: "arming", + }, ] if supported_features & SUPPORT_ALARM_ARM_HOME: triggers.append( @@ -122,29 +131,32 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Attach a trigger.""" config = TRIGGER_SCHEMA(config) + from_state = None if config[CONF_TYPE] == "triggered": - from_state = STATE_ALARM_PENDING to_state = STATE_ALARM_TRIGGERED elif config[CONF_TYPE] == "disarmed": - from_state = STATE_ALARM_TRIGGERED to_state = STATE_ALARM_DISARMED + elif config[CONF_TYPE] == "arming": + from_state = STATE_ALARM_DISARMED + to_state = STATE_ALARM_ARMING elif config[CONF_TYPE] == "armed_home": - from_state = STATE_ALARM_PENDING + from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING to_state = STATE_ALARM_ARMED_HOME elif config[CONF_TYPE] == "armed_away": - from_state = STATE_ALARM_PENDING + from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING to_state = STATE_ALARM_ARMED_AWAY elif config[CONF_TYPE] == "armed_night": - from_state = STATE_ALARM_PENDING + from_state = STATE_ALARM_PENDING or STATE_ALARM_ARMING to_state = STATE_ALARM_ARMED_NIGHT state_config = { state.CONF_PLATFORM: "state", CONF_ENTITY_ID: config[CONF_ENTITY_ID], - state.CONF_FROM: from_state, state.CONF_TO: to_state, } + if from_state: + state_config[state.CONF_FROM] = from_state state_config = state.TRIGGER_SCHEMA(state_config) return await state.async_attach_trigger( hass, state_config, action, automation_info, platform_type="device" diff --git a/homeassistant/components/demo/alarm_control_panel.py b/homeassistant/components/demo/alarm_control_panel.py index 0323b68b1b0..d5bb71da67b 100644 --- a/homeassistant/components/demo/alarm_control_panel.py +++ b/homeassistant/components/demo/alarm_control_panel.py @@ -3,8 +3,8 @@ import datetime from homeassistant.components.manual.alarm_control_panel import ManualAlarm from homeassistant.const import ( + CONF_ARMING_TIME, CONF_DELAY_TIME, - CONF_PENDING_TIME, CONF_TRIGGER_TIME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, @@ -28,18 +28,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= False, { STATE_ALARM_ARMED_AWAY: { + CONF_ARMING_TIME: datetime.timedelta(seconds=5), CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), }, STATE_ALARM_ARMED_HOME: { + CONF_ARMING_TIME: datetime.timedelta(seconds=5), CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), }, STATE_ALARM_ARMED_NIGHT: { + CONF_ARMING_TIME: datetime.timedelta(seconds=5), CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), }, STATE_ALARM_DISARMED: { @@ -47,12 +47,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), }, STATE_ALARM_ARMED_CUSTOM_BYPASS: { + CONF_ARMING_TIME: datetime.timedelta(seconds=5), CONF_DELAY_TIME: datetime.timedelta(seconds=0), - CONF_PENDING_TIME: datetime.timedelta(seconds=5), CONF_TRIGGER_TIME: datetime.timedelta(seconds=10), }, STATE_ALARM_TRIGGERED: { - CONF_PENDING_TIME: datetime.timedelta(seconds=5) + CONF_ARMING_TIME: datetime.timedelta(seconds=5) }, }, ) diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index b41da2d51bd..89f5e34213b 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -15,21 +15,23 @@ from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_TRIGGER, ) from homeassistant.const import ( + CONF_ARMING_TIME, CONF_CODE, CONF_DELAY_TIME, CONF_DISARM_AFTER_TRIGGER, CONF_NAME, - CONF_PENDING_TIME, CONF_PLATFORM, CONF_TRIGGER_TIME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, ) +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time from homeassistant.helpers.restore_state import RestoreEntity @@ -41,8 +43,8 @@ CONF_CODE_TEMPLATE = "code_template" CONF_CODE_ARM_REQUIRED = "code_arm_required" DEFAULT_ALARM_NAME = "HA Alarm" -DEFAULT_DELAY_TIME = datetime.timedelta(seconds=0) -DEFAULT_PENDING_TIME = datetime.timedelta(seconds=60) +DEFAULT_DELAY_TIME = datetime.timedelta(seconds=60) +DEFAULT_ARMING_TIME = datetime.timedelta(seconds=60) DEFAULT_TRIGGER_TIME = datetime.timedelta(seconds=120) DEFAULT_DISARM_AFTER_TRIGGER = False @@ -59,12 +61,14 @@ SUPPORTED_PRETRIGGER_STATES = [ state for state in SUPPORTED_STATES if state != STATE_ALARM_TRIGGERED ] -SUPPORTED_PENDING_STATES = [ - state for state in SUPPORTED_STATES if state != STATE_ALARM_DISARMED +SUPPORTED_ARMING_STATES = [ + state + for state in SUPPORTED_STATES + if state not in (STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) ] -ATTR_PRE_PENDING_STATE = "pre_pending_state" -ATTR_POST_PENDING_STATE = "post_pending_state" +ATTR_PREVIOUS_STATE = "previous_state" +ATTR_NEXT_STATE = "next_state" def _state_validator(config): @@ -75,9 +79,9 @@ def _state_validator(config): config[state][CONF_DELAY_TIME] = config[CONF_DELAY_TIME] if CONF_TRIGGER_TIME not in config[state]: config[state][CONF_TRIGGER_TIME] = config[CONF_TRIGGER_TIME] - for state in SUPPORTED_PENDING_STATES: - if CONF_PENDING_TIME not in config[state]: - config[state][CONF_PENDING_TIME] = config[CONF_PENDING_TIME] + for state in SUPPORTED_ARMING_STATES: + if CONF_ARMING_TIME not in config[state]: + config[state][CONF_ARMING_TIME] = config[CONF_ARMING_TIME] return config @@ -92,8 +96,8 @@ def _state_schema(state): schema[vol.Optional(CONF_TRIGGER_TIME)] = vol.All( cv.time_period, cv.positive_timedelta ) - if state in SUPPORTED_PENDING_STATES: - schema[vol.Optional(CONF_PENDING_TIME)] = vol.All( + if state in SUPPORTED_ARMING_STATES: + schema[vol.Optional(CONF_ARMING_TIME)] = vol.All( cv.time_period, cv.positive_timedelta ) return vol.Schema(schema) @@ -110,7 +114,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Optional(CONF_DELAY_TIME, default=DEFAULT_DELAY_TIME): vol.All( cv.time_period, cv.positive_timedelta ), - vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME): vol.All( + vol.Optional(CONF_ARMING_TIME, default=DEFAULT_ARMING_TIME): vol.All( cv.time_period, cv.positive_timedelta ), vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME): vol.All( @@ -164,9 +168,8 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): """ Representation of an alarm status. - When armed, will be pending for 'pending_time', after that armed. - When triggered, will be pending for the triggering state's 'delay_time' - plus the triggered state's 'pending_time'. + When armed, will be arming for 'arming_time', after that armed. + When triggered, will be pending for the triggering state's 'delay_time'. After that will be triggered for 'trigger_time', after that we return to the previous state or disarm if `disarm_after_trigger` is true. A trigger_time of zero disables the alarm_trigger service. @@ -204,9 +207,8 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): state: config[state][CONF_TRIGGER_TIME] for state in SUPPORTED_PRETRIGGER_STATES } - self._pending_time_by_state = { - state: config[state][CONF_PENDING_TIME] - for state in SUPPORTED_PENDING_STATES + self._arming_time_by_state = { + state: config[state][CONF_ARMING_TIME] for state in SUPPORTED_ARMING_STATES } @property @@ -234,10 +236,10 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): self._state = self._previous_state return self._state - if self._state in SUPPORTED_PENDING_STATES and self._within_pending_time( + if self._state in SUPPORTED_ARMING_STATES and self._within_arming_time( self._state ): - return STATE_ALARM_PENDING + return STATE_ALARM_ARMING return self._state @@ -255,16 +257,21 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): @property def _active_state(self): """Get the current state.""" - if self.state == STATE_ALARM_PENDING: + if self.state in (STATE_ALARM_PENDING, STATE_ALARM_ARMING): return self._previous_state return self._state + def _arming_time(self, state): + """Get the arming time.""" + return self._arming_time_by_state[state] + def _pending_time(self, state): """Get the pending time.""" - pending_time = self._pending_time_by_state[state] - if state == STATE_ALARM_TRIGGERED: - pending_time += self._delay_time_by_state[self._previous_state] - return pending_time + return self._delay_time_by_state[self._previous_state] + + def _within_arming_time(self, state): + """Get if the action is in the arming time window.""" + return self._state_ts + self._arming_time(state) > dt_util.utcnow() def _within_pending_time(self, state): """Get if the action is in the pending time window.""" @@ -350,22 +357,26 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): self._state_ts = dt_util.utcnow() self.schedule_update_ha_state() - pending_time = self._pending_time(state) if state == STATE_ALARM_TRIGGERED: + pending_time = self._pending_time(state) track_point_in_time( - self._hass, self.async_update_ha_state, self._state_ts + pending_time + self._hass, self.async_scheduled_update, self._state_ts + pending_time ) trigger_time = self._trigger_time_by_state[self._previous_state] track_point_in_time( self._hass, - self.async_update_ha_state, + self.async_scheduled_update, self._state_ts + pending_time + trigger_time, ) - elif state in SUPPORTED_PENDING_STATES and pending_time: - track_point_in_time( - self._hass, self.async_update_ha_state, self._state_ts + pending_time - ) + elif state in SUPPORTED_ARMING_STATES: + arming_time = self._arming_time(state) + if arming_time: + track_point_in_time( + self._hass, + self.async_scheduled_update, + self._state_ts + arming_time, + ) def _validate_code(self, code, state): """Validate given code.""" @@ -385,24 +396,32 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): """Return the state attributes.""" state_attr = {} - if self.state == STATE_ALARM_PENDING: - state_attr[ATTR_PRE_PENDING_STATE] = self._previous_state - state_attr[ATTR_POST_PENDING_STATE] = self._state + if self.state == STATE_ALARM_PENDING or self.state == STATE_ALARM_ARMING: + state_attr[ATTR_PREVIOUS_STATE] = self._previous_state + state_attr[ATTR_NEXT_STATE] = self._state return state_attr + @callback + def async_scheduled_update(self, now): + """Update state at a scheduled point in time.""" + self.async_write_ha_state() + async def async_added_to_hass(self): """Run when entity about to be added to hass.""" await super().async_added_to_hass() state = await self.async_get_last_state() if state: if ( - state.state == STATE_ALARM_PENDING + ( + state.state == STATE_ALARM_PENDING + or state.state == STATE_ALARM_ARMING + ) and hasattr(state, "attributes") - and state.attributes["pre_pending_state"] + and state.attributes[ATTR_PREVIOUS_STATE] ): - # If in pending state, we return to the pre_pending_state - self._state = state.attributes["pre_pending_state"] + # If in arming or pending state, we return to the ATTR_PREVIOUS_STATE + self._state = state.attributes[ATTR_PREVIOUS_STATE] self._state_ts = dt_util.utcnow() else: self._state = state.state diff --git a/homeassistant/const.py b/homeassistant/const.py index 1605fc751f8..442a4aa7fc2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -34,6 +34,7 @@ CONF_AFTER = "after" CONF_ALIAS = "alias" CONF_API_KEY = "api_key" CONF_API_VERSION = "api_version" +CONF_ARMING_TIME = "arming_time" CONF_AT = "at" CONF_AUTH_MFA_MODULES = "auth_mfa_modules" CONF_AUTH_PROVIDERS = "auth_providers" diff --git a/tests/components/alarm_control_panel/test_device_trigger.py b/tests/components/alarm_control_panel/test_device_trigger.py index 9b890aa4d25..9d414d179ab 100644 --- a/tests/components/alarm_control_panel/test_device_trigger.py +++ b/tests/components/alarm_control_panel/test_device_trigger.py @@ -69,6 +69,13 @@ async def test_get_triggers(hass, device_reg, entity_reg): "device_id": device_entry.id, "entity_id": f"{DOMAIN}.test_5678", }, + { + "platform": "device", + "domain": DOMAIN, + "type": "arming", + "device_id": device_entry.id, + "entity_id": f"{DOMAIN}.test_5678", + }, { "platform": "device", "domain": DOMAIN, diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index f1596277e3c..22dceec312b 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -9,6 +9,7 @@ from homeassistant.const import ( STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, @@ -41,7 +42,7 @@ async def test_arm_home_no_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -67,7 +68,7 @@ async def test_arm_home_no_pending_when_code_not_req(hass): "name": "test", "code": CODE, "code_arm_required": False, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -92,7 +93,7 @@ async def test_arm_home_with_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -104,10 +105,10 @@ async def test_arm_home_with_pending(hass): await common.async_alarm_arm_home(hass, CODE, entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state state = hass.states.get(entity_id) - assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_HOME + assert state.attributes["next_state"] == STATE_ALARM_ARMED_HOME future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -131,7 +132,7 @@ async def test_arm_home_with_invalid_code(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -156,7 +157,7 @@ async def test_arm_away_no_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -182,7 +183,7 @@ async def test_arm_away_no_pending_when_code_not_req(hass): "name": "test", "code": CODE, "code_arm_required": False, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -207,7 +208,7 @@ async def test_arm_home_with_template_code(hass): "platform": "manual", "name": "test", "code_template": '{{ "abc" }}', - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -233,7 +234,7 @@ async def test_arm_away_with_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -245,10 +246,10 @@ async def test_arm_away_with_pending(hass): await common.async_alarm_arm_away(hass, CODE) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state state = hass.states.get(entity_id) - assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_AWAY + assert state.attributes["next_state"] == STATE_ALARM_ARMED_AWAY future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -272,7 +273,7 @@ async def test_arm_away_with_invalid_code(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -297,7 +298,7 @@ async def test_arm_night_no_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -323,7 +324,7 @@ async def test_arm_night_no_pending_when_code_not_req(hass): "name": "test", "code": CODE, "code_arm_required": False, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -348,7 +349,7 @@ async def test_arm_night_with_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -360,10 +361,10 @@ async def test_arm_night_with_pending(hass): await common.async_alarm_arm_night(hass, CODE, entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state state = hass.states.get(entity_id) - assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_NIGHT + assert state.attributes["next_state"] == STATE_ALARM_ARMED_NIGHT future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -392,7 +393,7 @@ async def test_arm_night_with_invalid_code(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -452,7 +453,7 @@ async def test_trigger_with_delay(hass): "name": "test", "code": CODE, "delay_time": 1, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -470,7 +471,7 @@ async def test_trigger_with_delay(hass): state = hass.states.get(entity_id) assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -493,7 +494,7 @@ async def test_trigger_zero_trigger_time(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 0, + "arming_time": 0, "trigger_time": 0, "disarm_after_trigger": False, } @@ -518,7 +519,7 @@ async def test_trigger_zero_trigger_time_with_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 2, + "arming_time": 2, "trigger_time": 0, "disarm_after_trigger": False, } @@ -543,7 +544,7 @@ async def test_trigger_with_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 2, + "delay_time": 2, "trigger_time": 3, "disarm_after_trigger": False, } @@ -559,7 +560,7 @@ async def test_trigger_with_pending(hass): assert STATE_ALARM_PENDING == hass.states.get(entity_id).state state = hass.states.get(entity_id) - assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -595,7 +596,7 @@ async def test_trigger_with_unused_specific_delay(hass): "name": "test", "code": CODE, "delay_time": 5, - "pending_time": 0, + "arming_time": 0, "armed_home": {"delay_time": 10}, "disarm_after_trigger": False, } @@ -614,7 +615,7 @@ async def test_trigger_with_unused_specific_delay(hass): state = hass.states.get(entity_id) assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] future = dt_util.utcnow() + timedelta(seconds=5) with patch( @@ -639,7 +640,7 @@ async def test_trigger_with_specific_delay(hass): "name": "test", "code": CODE, "delay_time": 10, - "pending_time": 0, + "arming_time": 0, "armed_away": {"delay_time": 1}, "disarm_after_trigger": False, } @@ -658,7 +659,7 @@ async def test_trigger_with_specific_delay(hass): state = hass.states.get(entity_id) assert STATE_ALARM_PENDING == state.state - assert STATE_ALARM_TRIGGERED == state.attributes["post_pending_state"] + assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -682,9 +683,8 @@ async def test_trigger_with_pending_and_delay(hass): "platform": "manual", "name": "test", "code": CODE, - "delay_time": 1, - "pending_time": 0, - "triggered": {"pending_time": 1}, + "delay_time": 2, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -702,7 +702,7 @@ async def test_trigger_with_pending_and_delay(hass): state = hass.states.get(entity_id) assert state.state == STATE_ALARM_PENDING - assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -714,7 +714,7 @@ async def test_trigger_with_pending_and_delay(hass): state = hass.states.get(entity_id) assert state.state == STATE_ALARM_PENDING - assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -739,9 +739,8 @@ async def test_trigger_with_pending_and_specific_delay(hass): "name": "test", "code": CODE, "delay_time": 10, - "pending_time": 0, - "armed_away": {"delay_time": 1}, - "triggered": {"pending_time": 1}, + "arming_time": 0, + "armed_away": {"delay_time": 2}, "disarm_after_trigger": False, } }, @@ -759,7 +758,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): state = hass.states.get(entity_id) assert state.state == STATE_ALARM_PENDING - assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -771,7 +770,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): state = hass.states.get(entity_id) assert state.state == STATE_ALARM_PENDING - assert state.attributes["post_pending_state"] == STATE_ALARM_TRIGGERED + assert state.attributes["next_state"] == STATE_ALARM_TRIGGERED future += timedelta(seconds=1) with patch( @@ -794,8 +793,8 @@ async def test_armed_home_with_specific_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 10, - "armed_home": {"pending_time": 2}, + "arming_time": 10, + "armed_home": {"arming_time": 2}, } }, ) @@ -804,7 +803,7 @@ async def test_armed_home_with_specific_pending(hass): await common.async_alarm_arm_home(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -826,8 +825,8 @@ async def test_armed_away_with_specific_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 10, - "armed_away": {"pending_time": 2}, + "arming_time": 10, + "armed_away": {"arming_time": 2}, } }, ) @@ -836,7 +835,7 @@ async def test_armed_away_with_specific_pending(hass): await common.async_alarm_arm_away(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -858,8 +857,8 @@ async def test_armed_night_with_specific_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 10, - "armed_night": {"pending_time": 2}, + "arming_time": 10, + "armed_night": {"arming_time": 2}, } }, ) @@ -868,7 +867,7 @@ async def test_armed_night_with_specific_pending(hass): await common.async_alarm_arm_night(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -890,8 +889,8 @@ async def test_trigger_with_specific_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 10, - "triggered": {"pending_time": 2}, + "delay_time": 10, + "disarmed": {"delay_time": 2}, "trigger_time": 3, "disarm_after_trigger": False, } @@ -935,7 +934,7 @@ async def test_trigger_with_disarm_after_trigger(hass): "platform": "manual", "name": "test", "trigger_time": 5, - "pending_time": 0, + "delay_time": 0, "disarm_after_trigger": True, } }, @@ -971,7 +970,7 @@ async def test_trigger_with_zero_specific_trigger_time(hass): "name": "test", "trigger_time": 5, "disarmed": {"trigger_time": 0}, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": True, } }, @@ -997,7 +996,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): "name": "test", "trigger_time": 5, "armed_home": {"trigger_time": 0}, - "pending_time": 0, + "delay_time": 0, "disarm_after_trigger": True, } }, @@ -1032,7 +1031,7 @@ async def test_trigger_with_specific_trigger_time(hass): "platform": "manual", "name": "test", "disarmed": {"trigger_time": 5}, - "pending_time": 0, + "delay_time": 0, "disarm_after_trigger": True, } }, @@ -1067,7 +1066,8 @@ async def test_trigger_with_no_disarm_after_trigger(hass): "platform": "manual", "name": "test", "trigger_time": 5, - "pending_time": 0, + "arming_time": 0, + "delay_time": 0, "disarm_after_trigger": False, } }, @@ -1106,7 +1106,8 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): "platform": "manual", "name": "test", "trigger_time": 5, - "pending_time": 0, + "arming_time": 0, + "delay_time": 0, "disarm_after_trigger": False, } }, @@ -1196,7 +1197,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 5, + "delay_time": 5, "code": CODE + "2", "disarm_after_trigger": False, } @@ -1236,7 +1237,7 @@ async def test_disarm_with_template_code(hass): "platform": "manual", "name": "test", "code_template": '{{ "" if from_state == "disarmed" else "abc" }}', - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -1272,7 +1273,7 @@ async def test_arm_custom_bypass_no_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -1298,7 +1299,7 @@ async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): "name": "test", "code": CODE, "code_arm_required": False, - "pending_time": 0, + "arming_time": 0, "disarm_after_trigger": False, } }, @@ -1323,7 +1324,7 @@ async def test_arm_custom_bypass_with_pending(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -1335,10 +1336,10 @@ async def test_arm_custom_bypass_with_pending(hass): await common.async_alarm_arm_custom_bypass(hass, CODE, entity_id) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state state = hass.states.get(entity_id) - assert state.attributes["post_pending_state"] == STATE_ALARM_ARMED_CUSTOM_BYPASS + assert state.attributes["next_state"] == STATE_ALARM_ARMED_CUSTOM_BYPASS future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1362,7 +1363,7 @@ async def test_arm_custom_bypass_with_invalid_code(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 1, + "arming_time": 1, "disarm_after_trigger": False, } }, @@ -1386,8 +1387,8 @@ async def test_armed_custom_bypass_with_specific_pending(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 10, - "armed_custom_bypass": {"pending_time": 2}, + "arming_time": 10, + "armed_custom_bypass": {"arming_time": 2}, } }, ) @@ -1396,7 +1397,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): await common.async_alarm_arm_custom_bypass(hass) - assert STATE_ALARM_PENDING == hass.states.get(entity_id).state + assert STATE_ALARM_ARMING == hass.states.get(entity_id).state future = dt_util.utcnow() + timedelta(seconds=2) with patch( @@ -1419,9 +1420,9 @@ async def test_arm_away_after_disabled_disarmed(hass): "platform": "manual", "name": "test", "code": CODE, - "pending_time": 0, + "arming_time": 0, "delay_time": 1, - "armed_away": {"pending_time": 1}, + "armed_away": {"arming_time": 1}, "disarmed": {"trigger_time": 0}, "disarm_after_trigger": False, } @@ -1435,16 +1436,16 @@ async def test_arm_away_after_disabled_disarmed(hass): await common.async_alarm_arm_away(hass, CODE) 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_ALARM_ARMING == state.state + assert STATE_ALARM_DISARMED == state.attributes["previous_state"] + assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] 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_DISARMED == state.attributes["pre_pending_state"] - assert STATE_ALARM_ARMED_AWAY == state.attributes["post_pending_state"] + assert STATE_ALARM_ARMING == state.state + assert STATE_ALARM_DISARMED == state.attributes["previous_state"] + assert STATE_ALARM_ARMED_AWAY == state.attributes["next_state"] future = dt_util.utcnow() + timedelta(seconds=1) with patch( @@ -1461,8 +1462,8 @@ async def test_arm_away_after_disabled_disarmed(hass): 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_ALARM_ARMED_AWAY == state.attributes["previous_state"] + assert STATE_ALARM_TRIGGERED == state.attributes["next_state"] future += timedelta(seconds=1) with patch( @@ -1492,7 +1493,7 @@ async def test_restore_armed_state(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 0, + "arming_time": 0, "trigger_time": 0, "disarm_after_trigger": False, } @@ -1518,7 +1519,7 @@ async def test_restore_disarmed_state(hass): "alarm_control_panel": { "platform": "manual", "name": "test", - "pending_time": 0, + "arming_time": 0, "trigger_time": 0, "disarm_after_trigger": False, } From afe15a2e6bfd41fc081bcb89123c87a866ffd839 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 23 Apr 2020 15:52:36 -0700 Subject: [PATCH 030/511] Fix UVC doing I/O in event loop (#34610) --- homeassistant/components/uvc/camera.py | 29 ++++++++++++++++---------- tests/components/uvc/test_camera.py | 1 + 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index 431785f7c6a..05937cc3ee9 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -66,7 +66,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): [ UnifiVideoCamera(nvrconn, camera[identifier], camera["name"], password) for camera in cameras - ] + ], + True, ) return True @@ -85,17 +86,22 @@ class UnifiVideoCamera(Camera): self._connect_addr = None self._camera = None self._motion_status = False + self._caminfo = None @property def name(self): """Return the name of this camera.""" return self._name + @property + def should_poll(self): + """If this entity should be polled.""" + return True + @property def supported_features(self): """Return supported features.""" - caminfo = self._nvr.get_camera(self._uuid) - channels = caminfo["channels"] + channels = self._caminfo["channels"] for channel in channels: if channel["isRtspEnabled"]: return SUPPORT_STREAM @@ -105,14 +111,12 @@ class UnifiVideoCamera(Camera): @property def is_recording(self): """Return true if the camera is recording.""" - caminfo = self._nvr.get_camera(self._uuid) - return caminfo["recordingSettings"]["fullTimeRecordEnabled"] + return self._caminfo["recordingSettings"]["fullTimeRecordEnabled"] @property def motion_detection_enabled(self): """Camera Motion Detection Status.""" - caminfo = self._nvr.get_camera(self._uuid) - return caminfo["recordingSettings"]["motionRecordEnabled"] + return self._caminfo["recordingSettings"]["motionRecordEnabled"] @property def brand(self): @@ -122,13 +126,11 @@ class UnifiVideoCamera(Camera): @property def model(self): """Return the model of this camera.""" - caminfo = self._nvr.get_camera(self._uuid) - return caminfo["model"] + return self._caminfo["model"] def _login(self): """Login to the camera.""" - - caminfo = self._nvr.get_camera(self._uuid) + caminfo = self._caminfo if self._connect_addr: addrs = [self._connect_addr] else: @@ -164,6 +166,7 @@ class UnifiVideoCamera(Camera): return None self._camera = camera + self._caminfo = caminfo return True def camera_image(self): @@ -219,3 +222,7 @@ class UnifiVideoCamera(Camera): return channel["rtspUris"][0] return None + + def update(self): + """Update the info.""" + self._caminfo = self._nvr.get_camera(self._uuid) diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 2ca7fdf46e3..2b1e9c7782e 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -213,6 +213,7 @@ class TestUVC(unittest.TestCase): ], } self.nvr.server_version = (3, 2, 0) + self.uvc.update() def test_properties(self): """Test the properties.""" From 637ff5698c9afac1a0c5237efd99c3ae26ffee17 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 24 Apr 2020 01:00:17 +0200 Subject: [PATCH 031/511] Move myStrom light and switch to async (#34079) --- homeassistant/components/mystrom/const.py | 2 + homeassistant/components/mystrom/light.py | 54 ++++++++++-------- .../components/mystrom/manifest.json | 2 +- homeassistant/components/mystrom/switch.py | 55 ++++++++++--------- requirements_all.txt | 2 +- 5 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 homeassistant/components/mystrom/const.py diff --git a/homeassistant/components/mystrom/const.py b/homeassistant/components/mystrom/const.py new file mode 100644 index 00000000000..87697acbe96 --- /dev/null +++ b/homeassistant/components/mystrom/const.py @@ -0,0 +1,2 @@ +"""Constants for the myStrom integration.""" +DOMAIN = "mystrom" diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index 56fe369144b..72ec051b120 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -17,6 +17,7 @@ from homeassistant.components.light import ( Light, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME +from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -39,28 +40,29 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the myStrom Light platform.""" - +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the myStrom light integration.""" host = config.get(CONF_HOST) mac = config.get(CONF_MAC) name = config.get(CONF_NAME) bulb = MyStromBulb(host, mac) try: - if bulb.get_status()["type"] != "rgblamp": + await bulb.get_state() + if bulb.bulb_type != "rgblamp": _LOGGER.error("Device %s (%s) is not a myStrom bulb", host, mac) return except MyStromConnectionError: - _LOGGER.warning("No route to device: %s", host) + _LOGGER.warning("No route to myStrom bulb: %s", host) + raise PlatformNotReady() - add_entities([MyStromLight(bulb, name)], True) + async_add_entities([MyStromLight(bulb, name, mac)], True) class MyStromLight(Light): - """Representation of the myStrom WiFi Bulb.""" + """Representation of the myStrom WiFi bulb.""" - def __init__(self, bulb, name): + def __init__(self, bulb, name, mac): """Initialize the light.""" self._bulb = bulb self._name = name @@ -69,12 +71,18 @@ class MyStromLight(Light): self._brightness = 0 self._color_h = 0 self._color_s = 0 + self._mac = mac @property def name(self): """Return the display name of this light.""" return self._name + @property + def unique_id(self): + """Return a unique ID.""" + return self._mac + @property def supported_features(self): """Flag supported features.""" @@ -103,11 +111,10 @@ class MyStromLight(Light): @property def is_on(self): """Return true if light is on.""" - return self._state["on"] if self._state is not None else None + return self._state - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Turn on the light.""" - brightness = kwargs.get(ATTR_BRIGHTNESS, 255) effect = kwargs.get(ATTR_EFFECT) @@ -121,33 +128,32 @@ class MyStromLight(Light): try: if not self.is_on: - self._bulb.set_on() + await self._bulb.set_on() if brightness is not None: - self._bulb.set_color_hsv( + await self._bulb.set_color_hsv( int(color_h), int(color_s), round(brightness * 100 / 255) ) if effect == EFFECT_SUNRISE: - self._bulb.set_sunrise(30) + await self._bulb.set_sunrise(30) if effect == EFFECT_RAINBOW: - self._bulb.set_rainbow(30) + await self._bulb.set_rainbow(30) except MyStromConnectionError: - _LOGGER.warning("No route to device") + _LOGGER.warning("No route to myStrom bulb") - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Turn off the bulb.""" - try: - self._bulb.set_off() + await self._bulb.set_off() except MyStromConnectionError: _LOGGER.warning("myStrom bulb not online") - def update(self): + async def async_update(self): """Fetch new state data for this light.""" - try: - self._state = self._bulb.get_status() + await self._bulb.get_state() + self._state = self._bulb.state - colors = self._bulb.get_color()["color"] + colors = self._bulb.color try: color_h, color_s, color_v = colors.split(";") except ValueError: @@ -160,5 +166,5 @@ class MyStromLight(Light): self._available = True except MyStromConnectionError: - _LOGGER.warning("No route to device") + _LOGGER.warning("No route to myStrom bulb") self._available = False diff --git a/homeassistant/components/mystrom/manifest.json b/homeassistant/components/mystrom/manifest.json index 7d74fca92a2..71a719be92a 100644 --- a/homeassistant/components/mystrom/manifest.json +++ b/homeassistant/components/mystrom/manifest.json @@ -2,7 +2,7 @@ "domain": "mystrom", "name": "myStrom", "documentation": "https://www.home-assistant.io/integrations/mystrom", - "requirements": ["python-mystrom==0.5.0"], + "requirements": ["python-mystrom==1.1.2"], "dependencies": ["http"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/mystrom/switch.py b/homeassistant/components/mystrom/switch.py index 5bfd1e45b81..ea91c9ba5d7 100644 --- a/homeassistant/components/mystrom/switch.py +++ b/homeassistant/components/mystrom/switch.py @@ -1,8 +1,8 @@ -"""Support for myStrom switches.""" +"""Support for myStrom switches/plugs.""" import logging from pymystrom.exceptions import MyStromConnectionError -from pymystrom.switch import MyStromPlug +from pymystrom.switch import MyStromSwitch as _MyStromSwitch import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice @@ -22,30 +22,30 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Find and return myStrom switch.""" +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the myStrom switch/plug integration.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) try: - MyStromPlug(host).get_status() + plug = _MyStromSwitch(host) + await plug.get_state() except MyStromConnectionError: - _LOGGER.error("No route to device: %s", host) + _LOGGER.error("No route to myStrom plug: %s", host) raise PlatformNotReady() - add_entities([MyStromSwitch(name, host)]) + async_add_entities([MyStromSwitch(plug, name)]) class MyStromSwitch(SwitchDevice): - """Representation of a myStrom switch.""" + """Representation of a myStrom switch/plug.""" - def __init__(self, name, resource): - """Initialize the myStrom switch.""" + def __init__(self, plug, name): + """Initialize the myStrom switch/plug.""" self._name = name - self._resource = resource - self.data = {} - self.plug = MyStromPlug(self._resource) + self.plug = plug self._available = True + self.relay = None @property def name(self): @@ -55,38 +55,43 @@ class MyStromSwitch(SwitchDevice): @property def is_on(self): """Return true if switch is on.""" - return bool(self.data["relay"]) + return bool(self.relay) + + @property + def unique_id(self): + """Return a unique ID.""" + return self.plug._mac # pylint: disable=protected-access @property def current_power_w(self): """Return the current power consumption in W.""" - return round(self.data["power"], 2) + return self.plug.consumption @property def available(self): """Could the device be accessed during the last update call.""" return self._available - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Turn the switch on.""" try: - self.plug.set_relay_on() + await self.plug.turn_on() except MyStromConnectionError: - _LOGGER.error("No route to device: %s", self._resource) + _LOGGER.error("No route to myStrom plug") - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Turn the switch off.""" try: - self.plug.set_relay_off() + await self.plug.turn_off() except MyStromConnectionError: - _LOGGER.error("No route to device: %s", self._resource) + _LOGGER.error("No route to myStrom plug") - def update(self): + async def async_update(self): """Get the latest data from the device and update the data.""" try: - self.data = self.plug.get_status() + await self.plug.get_state() + self.relay = self.plug.relay self._available = True except MyStromConnectionError: - self.data = {"power": 0, "relay": False} self._available = False - _LOGGER.error("No route to device: %s", self._resource) + _LOGGER.error("No route to myStrom plug") diff --git a/requirements_all.txt b/requirements_all.txt index d64bdbd11f4..dcc2a8ae24d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1659,7 +1659,7 @@ python-miio==0.5.0.1 python-mpd2==1.0.0 # homeassistant.components.mystrom -python-mystrom==0.5.0 +python-mystrom==1.1.2 # homeassistant.components.nest python-nest==4.1.0 From 5b7a4434c4dff4e2df3a3682ed7f109048fd02ef Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 24 Apr 2020 01:00:57 +0200 Subject: [PATCH 032/511] Fix BloomSky KeyError: 'monitored_conditions' (#34613) Co-Authored-By: Paulus Schoutsen --- homeassistant/components/bloomsky/binary_sensor.py | 3 +++ homeassistant/components/bloomsky/camera.py | 3 +++ homeassistant/components/bloomsky/sensor.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index b98bb688ca3..077171006bf 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -25,6 +25,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the available BloomSky weather binary sensors.""" # Default needed in case of discovery + if discovery_info is not None: + return + sensors = config[CONF_MONITORED_CONDITIONS] bloomsky = hass.data[DOMAIN] diff --git a/homeassistant/components/bloomsky/camera.py b/homeassistant/components/bloomsky/camera.py index 43bae9dc3bf..e14e2f5c68b 100644 --- a/homeassistant/components/bloomsky/camera.py +++ b/homeassistant/components/bloomsky/camera.py @@ -10,6 +10,9 @@ from . import DOMAIN def setup_platform(hass, config, add_entities, discovery_info=None): """Set up access to BloomSky cameras.""" + if discovery_info is not None: + return + bloomsky = hass.data[DOMAIN] for device in bloomsky.devices.values(): diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 2b4563dab83..0a2c19a8cd8 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -60,6 +60,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the available BloomSky weather sensors.""" # Default needed in case of discovery + if discovery_info is not None: + return + sensors = config[CONF_MONITORED_CONDITIONS] bloomsky = hass.data[DOMAIN] From 5ba0ccd43b414b8c3885f4003a26f802ad4035ef Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 24 Apr 2020 00:04:36 +0000 Subject: [PATCH 033/511] [ci skip] Translation update --- .../components/airvisual/translations/ca.json | 26 ++++++++++++++-- .../components/airvisual/translations/es.json | 26 ++++++++++++++-- .../components/airvisual/translations/no.json | 26 ++++++++++++++-- .../components/airvisual/translations/pl.json | 3 +- .../components/airvisual/translations/ru.json | 30 ++++++++++++++++--- .../airvisual/translations/zh-Hant.json | 30 ++++++++++++++++--- .../components/atag/translations/es.json | 20 +++++++++++++ .../components/atag/translations/no.json | 20 +++++++++++++ .../components/atag/translations/ru.json | 20 +++++++++++++ .../components/atag/translations/zh-Hant.json | 20 +++++++++++++ .../components/cover/translations/no.json | 6 ++++ .../homekit_controller/translations/ru.json | 2 +- .../synology_dsm/translations/es.json | 4 ++- .../synology_dsm/translations/no.json | 4 ++- .../synology_dsm/translations/ru.json | 4 ++- .../synology_dsm/translations/zh-Hant.json | 4 ++- .../components/upnp/translations/zh-Hant.json | 2 +- 17 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/atag/translations/es.json create mode 100644 homeassistant/components/atag/translations/no.json create mode 100644 homeassistant/components/atag/translations/ru.json create mode 100644 homeassistant/components/atag/translations/zh-Hant.json diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index d8866051c6c..2d9a644c704 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -4,15 +4,37 @@ "already_configured": "Aquesta clau API ja est\u00e0 sent utilitzada." }, "error": { - "invalid_api_key": "Clau API inv\u00e0lida" + "general_error": "S'ha produ\u00eft un error desconegut.", + "invalid_api_key": "Clau API inv\u00e0lida", + "unable_to_connect": "No s'ha pogut connectar a la unitat Node/Pro." }, "step": { - "user": { + "geography": { "data": { "api_key": "Clau API", "latitude": "Latitud", "longitude": "Longitud" }, + "description": "Utilitza l'API d'AirVisual per monitoritzar una ubicaci\u00f3 geogr\u00e0fica.", + "title": "Configuraci\u00f3 localitzaci\u00f3 geogr\u00e0fica" + }, + "node_pro": { + "data": { + "ip_address": "Adre\u00e7a IP o amfitri\u00f3 de la unitat", + "password": "Contrasenya de la unitat" + }, + "description": "Monitoritza una unitat personal d'AirVisual. Pots obtenir la contrasenya des de la interf\u00edcie d'usuari (UI) de la unitat.", + "title": "Configuraci\u00f3 d'AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "Clau API", + "cloud_api": "Ubicaci\u00f3 geogr\u00e0fica", + "latitude": "Latitud", + "longitude": "Longitud", + "node_pro": "AirVisual Node Pro", + "type": "Tipus d'integraci\u00f3" + }, "description": "Monitoritzaci\u00f3 de la qualitat de l'aire per ubicaci\u00f3 geogr\u00e0fica.", "title": "Configura AirVisual" } diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 2c442f5c3a5..e64b2f31691 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -4,15 +4,37 @@ "already_configured": "Esta clave API ya est\u00e1 en uso." }, "error": { - "invalid_api_key": "Clave API inv\u00e1lida" + "general_error": "Se ha producido un error desconocido.", + "invalid_api_key": "Clave API inv\u00e1lida", + "unable_to_connect": "No se puede conectar a la unidad Node/Pro." }, "step": { - "user": { + "geography": { "data": { "api_key": "Clave API", "latitude": "Latitud", "longitude": "Longitud" }, + "description": "Utilizar la API en la nube de AirVisual para monitorizar una ubicaci\u00f3n geogr\u00e1fica.", + "title": "Configurar una Geograf\u00eda" + }, + "node_pro": { + "data": { + "ip_address": "Direcci\u00f3n IP/Nombre de host de la Unidad", + "password": "Contrase\u00f1a de la Unidad" + }, + "description": "Monitorizar una unidad personal AirVisual. La contrase\u00f1a puede ser recuperada desde la interfaz de la unidad.", + "title": "Configurar un AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "Clave API", + "cloud_api": "Ubicaci\u00f3n Geogr\u00e1fica", + "latitude": "Latitud", + "longitude": "Longitud", + "node_pro": "AirVisual Node Pro", + "type": "Tipo de Integraci\u00f3n" + }, "description": "Monitorizar la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.", "title": "Configurar AirVisual" } diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index 1a9463da15e..77ab1425c87 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -4,15 +4,37 @@ "already_configured": "Disse koordinatene er allerede registrert." }, "error": { - "invalid_api_key": "Ugyldig API-n\u00f8kkel" + "general_error": "Det oppstod en ukjent feil.", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", + "unable_to_connect": "Kan ikke koble til Node / Pro-enheten." }, "step": { - "user": { + "geography": { "data": { "api_key": "API-n\u00f8kkel", "latitude": "Breddegrad", "longitude": "Lengdegrad" }, + "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en geografisk plassering.", + "title": "Konfigurer en geografi" + }, + "node_pro": { + "data": { + "ip_address": "Enhetens IP-adresse / vertsnavn", + "password": "Passord for enhet" + }, + "description": "Overv\u00e5ke en personlig AirVisual-enhet. Passordet kan hentes fra enhetens brukergrensesnitt.", + "title": "Konfigurer en AirVisual Node / Pro" + }, + "user": { + "data": { + "api_key": "API-n\u00f8kkel", + "cloud_api": "Geografisk plassering", + "latitude": "Breddegrad", + "longitude": "Lengdegrad", + "node_pro": "AirVisual Node Pro", + "type": "Integrasjonstype" + }, "description": "Overv\u00e5k luftkvaliteten p\u00e5 et geografisk sted.", "title": "Konfigurer AirVisual" } diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index 8258c0e86ed..cb73ed656f2 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -11,7 +11,8 @@ "data": { "api_key": "Klucz API", "latitude": "Szeroko\u015b\u0107 geograficzna", - "longitude": "D\u0142ugo\u015b\u0107 geograficzna" + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "type": "Typ integracji" }, "description": "Monitoruj jako\u015b\u0107 powietrza w okre\u015blonej lokalizacji geograficznej.", "title": "Konfiguracja AirVisual" diff --git a/homeassistant/components/airvisual/translations/ru.json b/homeassistant/components/airvisual/translations/ru.json index 1d2a5edd96d..3479d59ee90 100644 --- a/homeassistant/components/airvisual/translations/ru.json +++ b/homeassistant/components/airvisual/translations/ru.json @@ -1,19 +1,41 @@ { "config": { "abort": { - "already_configured": "\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b." + "already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e." }, "error": { - "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API." + "general_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "unable_to_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, "step": { - "user": { + "geography": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430" }, - "description": "\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0439\u0442\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u0437\u0434\u0443\u0445\u0430 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438.", + "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e API AirVisual.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" + }, + "node_pro": { + "data": { + "ip_address": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c" + }, + "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0412\u0430\u0448\u0435\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AirVisual Node / Pro" + }, + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "cloud_api": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", + "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", + "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", + "node_pro": "AirVisual Node Pro", + "type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 AirVisual, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c.", "title": "AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index a655743685c..1153d7c3b99 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -1,19 +1,41 @@ { "config": { "abort": { - "already_configured": "\u6b64\u4e9b\u5ea7\u6a19\u5df2\u8a3b\u518a\u3002" + "already_configured": "\u6b64\u5ea7\u6a19\u6216 Node/Pro ID \u5df2\u8a3b\u518a\u3002" }, "error": { - "invalid_api_key": "API \u5bc6\u78bc\u7121\u6548" + "general_error": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002", + "invalid_api_key": "API \u5bc6\u78bc\u7121\u6548\u3002", + "unable_to_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Node/Pro \u8a2d\u5099\u3002" }, "step": { - "user": { + "geography": { "data": { "api_key": "API \u5bc6\u9470", "latitude": "\u7def\u5ea6", "longitude": "\u7d93\u5ea6" }, - "description": "\u4f9d\u5730\u7406\u4f4d\u7f6e\u76e3\u63a7\u7a7a\u6c23\u54c1\u8cea\u3002", + "description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u5730\u7406\u5ea7\u6a19\u3002", + "title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19" + }, + "node_pro": { + "data": { + "ip_address": "\u8a2d\u5099 IP \u4f4d\u5740/\u4e3b\u6a5f\u540d\u7a31", + "password": "\u8a2d\u5099\u5bc6\u78bc" + }, + "description": "\u76e3\u63a7\u500b\u4eba AirVisual \u8a2d\u5099\uff0c\u5bc6\u78bc\u53ef\u4ee5\u900f\u904e\u8a2d\u5099 UI \u7372\u5f97\u3002", + "title": "\u8a2d\u5b9a AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "cloud_api": "\u5730\u7406\u5ea7\u6a19", + "latitude": "\u7def\u5ea6", + "longitude": "\u7d93\u5ea6", + "node_pro": "AirVisual Node Pro", + "type": "\u6574\u5408\u985e\u578b" + }, + "description": "\u9078\u64c7\u6240\u8981\u76e3\u63a7\u7684 AirVisual \u8cc7\u6599\u985e\u578b\u3002", "title": "\u8a2d\u5b9a AirVisual" } } diff --git a/homeassistant/components/atag/translations/es.json b/homeassistant/components/atag/translations/es.json new file mode 100644 index 00000000000..b02a20e09a1 --- /dev/null +++ b/homeassistant/components/atag/translations/es.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "S\u00f3lo se puede a\u00f1adir un dispositivo Atag a Home Assistant" + }, + "error": { + "connection_error": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto (10000)" + }, + "title": "Conectarse al dispositivo" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/no.json b/homeassistant/components/atag/translations/no.json new file mode 100644 index 00000000000..4b4a5346558 --- /dev/null +++ b/homeassistant/components/atag/translations/no.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Bare en Atag-enhet kan legges til Home Assistant" + }, + "error": { + "connection_error": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "port": "Port (10000)" + }, + "title": "Koble til enheten" + } + } + }, + "title": "Atag " +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/ru.json b/homeassistant/components/atag/translations/ru.json new file mode 100644 index 00000000000..f1c734dc933 --- /dev/null +++ b/homeassistant/components/atag/translations/ru.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u041c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e." + }, + "error": { + "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442 (10000)" + }, + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/zh-Hant.json b/homeassistant/components/atag/translations/zh-Hant.json new file mode 100644 index 00000000000..aa1c6a90d2b --- /dev/null +++ b/homeassistant/components/atag/translations/zh-Hant.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u50c5\u80fd\u65b0\u589e\u4e00\u7d44 Atag \u8a2d\u5099\u81f3 Home Assistant" + }, + "error": { + "connection_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0\uff0810000\uff09" + }, + "title": "\u9023\u7dda\u81f3\u8a2d\u5099" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/no.json b/homeassistant/components/cover/translations/no.json index e13feac92b8..840ede020f4 100644 --- a/homeassistant/components/cover/translations/no.json +++ b/homeassistant/components/cover/translations/no.json @@ -25,5 +25,11 @@ "tilt_position": "{entity_name} endringer i vippeposisjon" } }, + "state": { + "_": { + "closing": "Lukker", + "stopped": "Stoppet" + } + }, "title": "Dekke" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ru.json b/homeassistant/components/homekit_controller/translations/ru.json index d37ef1b014c..95f8a4d6efe 100644 --- a/homeassistant/components/homekit_controller/translations/ru.json +++ b/homeassistant/components/homekit_controller/translations/ru.json @@ -36,5 +36,5 @@ } } }, - "title": "\u0410\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440 HomeKit" + "title": "HomeKit Controller" } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index 5a045e14a98..ad15de831c4 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -4,9 +4,11 @@ "already_configured": "El host ya est\u00e1 configurado." }, "error": { + "connection": "Error de conexi\u00f3n: comprueba tu host, contrase\u00f1a y ssl", "login": "Error de inicio de sesi\u00f3n: comprueba tu nombre de usuario y contrase\u00f1a", "missing_data": "Faltan datos: por favor, vuelva a intentarlo m\u00e1s tarde o pruebe con otra configuraci\u00f3n", - "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso" + "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso", + "unknown": "Error desconocido: por favor, consulta logs para obtener m\u00e1s detalles" }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index f0f19754b0b..5b79e58e121 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -4,9 +4,11 @@ "already_configured": "Verten er allerede konfigurert" }, "error": { + "connection": "Tilkoblingsfeil: sjekk verten, passordet og ssl", "login": "P\u00e5loggingsfeil: Vennligst sjekk brukernavnet ditt og passordet ditt", "missing_data": "Manglende data: Pr\u00f8v p\u00e5 nytt senere eller en annen konfigurasjon", - "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode" + "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode", + "unknown": "Ukjent feil: sjekk loggene for \u00e5 f\u00e5 flere detaljer" }, "flow_title": "Synology DSM {name} ( {host} )", "step": { diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 2ac458963f2..837af047853 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -4,9 +4,11 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { + "connection": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0445\u043e\u0441\u0442, \u043f\u0430\u0440\u043e\u043b\u044c \u0438 SSL.", "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "missing_data": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", - "otp_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \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, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c." + "otp_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \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, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", + "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index 1d1bde6a26f..6ca0b54106d 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -4,9 +4,11 @@ "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "connection": "\u9023\u7dda\u932f\u8aa4\uff1a\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u3001\u5bc6\u78bc\u8207 SSL", "login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u5bc6\u78bc", "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": "\u5169\u6b65\u9a5f\u9a57\u8b49\u5931\u6557\uff0c\u8acb\u91cd\u65b0\u53d6\u5f97\u4ee3\u78bc\u5f8c\u91cd\u8a66", + "unknown": "\u672a\u77e5\u932f\u8aa4\uff1a\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8cc7\u8a0a" }, "flow_title": "\u7fa4\u6689 DSM {name} ({host})", "step": { diff --git a/homeassistant/components/upnp/translations/zh-Hant.json b/homeassistant/components/upnp/translations/zh-Hant.json index 74911d92d04..5464ce9e74b 100644 --- a/homeassistant/components/upnp/translations/zh-Hant.json +++ b/homeassistant/components/upnp/translations/zh-Hant.json @@ -22,7 +22,7 @@ "enable_sensors": "\u65b0\u589e\u6d41\u91cf\u611f\u61c9\u5668", "igd": "UPnP/IGD" }, - "title": "UPnP/IGD \u8a2d\u5b9a\u9078\u9805" + "title": "\u8a2d\u5b9a\u9078\u9805" } } } From f94329dbbde5cfafffeb7a4d8a7d95094ce65b24 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 23 Apr 2020 19:53:18 -0500 Subject: [PATCH 034/511] Handle synology_dsm discovery broadcasting on multiple ip addresses (#34623) --- .../components/synology_dsm/config_flow.py | 9 ++++++- .../synology_dsm/test_config_flow.py | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index c1e8cf553d9..025b2959026 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -133,7 +133,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._show_setup_form(user_input, errors) # Check if already configured - await self.async_set_unique_id(serial) + await self.async_set_unique_id(serial, raise_on_progress=False) self._abort_if_unique_id_configured() config_data = { @@ -162,6 +162,13 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if self._host_already_configured(parsed_url.hostname): return self.async_abort(reason="already_configured") + if ssdp.ATTR_UPNP_SERIAL in discovery_info: + # Synology can broadcast on multiple IP addresses + await self.async_set_unique_id( + discovery_info[ssdp.ATTR_UPNP_SERIAL].upper() + ) + self._abort_if_unique_id_configured() + self.discovered_conf = { CONF_NAME: friendly_name, CONF_HOST: parsed_url.hostname, diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 9a9283256c5..66f752ffaf4 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -321,6 +321,30 @@ async def test_missing_data_after_login( assert result["errors"] == {"base": "missing_data"} +async def test_form_ssdp_already_configured( + hass: HomeAssistantType, service: MagicMock +): + """Test ssdp abort when the serial number is already configured.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + MockConfigEntry( + domain=DOMAIN, + data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, + unique_id=SERIAL.upper(), + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_SSDP}, + data={ + ssdp.ATTR_SSDP_LOCATION: "http://192.168.1.5:5000", + ssdp.ATTR_UPNP_FRIENDLY_NAME: "mydsm", + ssdp.ATTR_UPNP_SERIAL: SERIAL, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock): """Test we can setup from ssdp.""" await setup.async_setup_component(hass, "persistent_notification", {}) @@ -331,6 +355,7 @@ async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock): data={ ssdp.ATTR_SSDP_LOCATION: "http://192.168.1.5:5000", ssdp.ATTR_UPNP_FRIENDLY_NAME: "mydsm", + ssdp.ATTR_UPNP_SERIAL: SERIAL, }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM From c582911879f1e67af428c9bbbb6384aa2c2ab2d6 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 23 Apr 2020 20:12:39 -0500 Subject: [PATCH 035/511] Refactor Plex device/session updates (#34616) --- homeassistant/components/plex/server.py | 25 +++++++++++++------------ tests/components/plex/test_server.py | 10 +++++++++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index ca5e4756cf2..dc252d57410 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -207,36 +207,37 @@ class PlexServer: ) return - for device in devices: + def process_device(source, device): self._known_idle.discard(device.machineIdentifier) - available_clients[device.machineIdentifier] = {"device": device} + available_clients.setdefault(device.machineIdentifier, {"device": device}) if device.machineIdentifier not in self._known_clients: new_clients.add(device.machineIdentifier) - _LOGGER.debug("New device: %s", device.machineIdentifier) + _LOGGER.debug( + "New %s %s: %s", device.product, source, device.machineIdentifier + ) + + for device in devices: + process_device("device", device) for session in sessions: if session.TYPE == "photo": _LOGGER.debug("Photo session detected, skipping: %s", session) continue + session_username = session.usernames[0] for player in session.players: if session_username and session_username not in monitored_users: ignored_clients.add(player.machineIdentifier) _LOGGER.debug( - "Ignoring Plex client owned by '%s'", session_username + "Ignoring %s client owned by '%s'", + player.product, + session_username, ) continue - self._known_idle.discard(player.machineIdentifier) - available_clients.setdefault( - player.machineIdentifier, {"device": player} - ) + process_device("session", player) available_clients[player.machineIdentifier]["session"] = session - if player.machineIdentifier not in self._known_clients: - new_clients.add(player.machineIdentifier) - _LOGGER.debug("New session: %s", player.machineIdentifier) - new_entity_configs = [] for client_id, client_data in available_clients.items(): if client_id in ignored_clients: diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index 6eff97ae7dc..bc3f9e39fe0 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -93,7 +93,15 @@ async def test_new_ignored_users_available(hass, caplog): assert len(monitored_users) == 1 assert len(ignored_users) == 2 for ignored_user in ignored_users: - assert f"Ignoring Plex client owned by '{ignored_user}'" in caplog.text + ignored_client = [ + x.players[0] + for x in mock_plex_server.sessions() + if x.usernames[0] in ignored_users + ][0] + assert ( + f"Ignoring {ignored_client.product} client owned by '{ignored_user}'" + in caplog.text + ) sensor = hass.states.get("sensor.plex_plex_server_1") assert sensor.state == str(len(mock_plex_server.accounts)) From 813e945d86bdff0ef512dc176912d06cc8d11c75 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 24 Apr 2020 02:46:07 -0500 Subject: [PATCH 036/511] Remove deprecated cert_expiry config (#34628) --- homeassistant/components/cert_expiry/sensor.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 39ec2c35ac7..ec1e9110317 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -8,7 +8,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( CONF_HOST, - CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_START, TIME_DAYS, @@ -27,15 +26,11 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(hours=12) -PLATFORM_SCHEMA = vol.All( - cv.deprecated(CONF_NAME, invalidation_version="0.109"), - PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - } - ), +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } ) From d3bbd9ec65cd9d690b6ed0b87e12630063cc7758 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 01:49:11 -0700 Subject: [PATCH 037/511] Delay sync for Google and limit updates to relevant info (#34622) --- .../components/cloud/alexa_config.py | 16 ++++++++-- .../components/cloud/google_config.py | 21 ++++++++----- homeassistant/helpers/entity_registry.py | 12 ++++++++ tests/components/cloud/test_alexa_config.py | 11 +++++++ tests/components/cloud/test_google_config.py | 30 ++++++++++++------- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/cloud/alexa_config.py b/homeassistant/components/cloud/alexa_config.py index dc1f5c11b5d..a45469c8f97 100644 --- a/homeassistant/components/cloud/alexa_config.py +++ b/homeassistant/components/cloud/alexa_config.py @@ -271,15 +271,25 @@ class AlexaConfig(alexa_config.AbstractConfig): if not self.enabled or not self._cloud.is_logged_in: return - action = event.data["action"] entity_id = event.data["entity_id"] + + if not self.should_expose(entity_id): + return + + action = event.data["action"] to_update = [] to_remove = [] - if action == "create" and self.should_expose(entity_id): + if action == "create": to_update.append(entity_id) - elif action == "remove" and self.should_expose(entity_id): + elif action == "remove": to_remove.append(entity_id) + elif action == "update" and bool( + set(event.data["changes"]) & entity_registry.ENTITY_DESCRIBING_ATTRIBUTES + ): + to_update.append(entity_id) + if "old_entity_id" in event.data: + to_remove.append(event.data["old_entity_id"]) try: await self._sync_helper(to_update, to_remove) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index 8420a1bea7e..bb6dcaa2fe2 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -33,12 +33,6 @@ class CloudGoogleConfig(AbstractConfig): self._cur_entity_prefs = self._prefs.google_entity_configs self._sync_entities_lock = asyncio.Lock() - prefs.async_listen_updates(self._async_prefs_updated) - hass.bus.async_listen( - entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, - self._handle_entity_registry_updated, - ) - @property def enabled(self): """Return if Google is enabled.""" @@ -83,6 +77,13 @@ class CloudGoogleConfig(AbstractConfig): # Remove bad data that was there until 0.103.6 - Jan 6, 2020 self._store.pop_agent_user_id(self._user) + self._prefs.async_listen_updates(self._async_prefs_updated) + + self.hass.bus.async_listen( + entity_registry.EVENT_ENTITY_REGISTRY_UPDATED, + self._handle_entity_registry_updated, + ) + def should_expose(self, state): """If a state object should be exposed.""" return self._should_expose_entity_id(state.entity_id) @@ -160,8 +161,14 @@ class CloudGoogleConfig(AbstractConfig): if not self.enabled or not self._cloud.is_logged_in: return + # Only consider entity registry updates if info relevant for Google has changed + if event.data["action"] == "update" and not bool( + set(event.data["changes"]) & entity_registry.ENTITY_DESCRIBING_ATTRIBUTES + ): + return + entity_id = event.data["entity_id"] # Schedule a sync if a change was made to an entity that Google knows about if self._should_expose_entity_id(entity_id): - await self.async_sync_entities_all() + self.async_schedule_google_sync_all() diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index d73812c207b..10de8564fca 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -62,6 +62,18 @@ ATTR_RESTORED = "restored" STORAGE_VERSION = 1 STORAGE_KEY = "core.entity_registry" +# Attributes relevant to describing entity +# to external services. +ENTITY_DESCRIBING_ATTRIBUTES = { + "entity_id", + "name", + "original_name", + "capabilities", + "supported_features", + "device_class", + "unit_of_measurement", +} + @attr.s(slots=True, frozen=True) class RegistryEntry: diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index 508626b43f0..f65b810d690 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -165,10 +165,21 @@ async def test_alexa_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): "action": "update", "entity_id": "light.kitchen", "changes": ["entity_id"], + "old_entity_id": "light.living_room", }, ) await hass.async_block_till_done() + assert to_update == ["light.kitchen"] + assert to_remove == ["light.living_room"] + + with patch_sync_helper() as (to_update, to_remove): + hass.bus.async_fire( + EVENT_ENTITY_REGISTRY_UPDATED, + {"action": "update", "entity_id": "light.kitchen", "changes": ["icon"]}, + ) + await hass.async_block_till_done() + assert to_update == [] assert to_remove == [] diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 1070730ba96..2474851cce8 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,5 +1,7 @@ """Test the Cloud Google Config.""" -from unittest.mock import Mock, patch +from unittest.mock import Mock + +from asynctest import patch from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig @@ -105,30 +107,27 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): await config.async_connect_agent_user("mock-user-id") with patch.object( - config, "async_sync_entities", side_effect=mock_coro + config, "async_schedule_google_sync_all", side_effect=mock_coro ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + # Created entity hass.bus.async_fire( EVENT_ENTITY_REGISTRY_UPDATED, {"action": "create", "entity_id": "light.kitchen"}, ) await hass.async_block_till_done() - assert len(mock_sync.mock_calls) == 1 + assert len(mock_sync.mock_calls) == 1 - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + # Removed entity hass.bus.async_fire( EVENT_ENTITY_REGISTRY_UPDATED, {"action": "remove", "entity_id": "light.kitchen"}, ) await hass.async_block_till_done() - assert len(mock_sync.mock_calls) == 1 + assert len(mock_sync.mock_calls) == 2 - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + # Entity registry updated with relevant changes hass.bus.async_fire( EVENT_ENTITY_REGISTRY_UPDATED, { @@ -139,4 +138,13 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): ) await hass.async_block_till_done() - assert len(mock_sync.mock_calls) == 1 + assert len(mock_sync.mock_calls) == 3 + + # Entity registry updated with non-relevant changes + hass.bus.async_fire( + EVENT_ENTITY_REGISTRY_UPDATED, + {"action": "update", "entity_id": "light.kitchen", "changes": ["icon"]}, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 3 From d416029e82aba7db4dace31dd7645fa8a9661b7e Mon Sep 17 00:00:00 2001 From: Geronimo2015 Date: Fri, 24 Apr 2020 17:28:44 +0200 Subject: [PATCH 038/511] Add onvif PTZ GotoPreset (#34420) * Added PTZ GotoPreset support * Update camera.py Processed flake8 error * Update services.yaml Removed trailing spaces * Update camera.py black formatted code --- homeassistant/components/onvif/camera.py | 23 ++++++++++++++++---- homeassistant/components/onvif/services.yaml | 5 ++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 51ea3fab0cc..7fea75735e0 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -54,6 +54,7 @@ ATTR_DISTANCE = "distance" ATTR_SPEED = "speed" ATTR_MOVE_MODE = "move_mode" ATTR_CONTINUOUS_DURATION = "continuous_duration" +ATTR_PRESET = "preset" DIR_UP = "UP" DIR_DOWN = "DOWN" @@ -67,6 +68,7 @@ ZOOM_FACTOR = {ZOOM_IN: 1, ZOOM_OUT: -1} CONTINUOUS_MOVE = "ContinuousMove" RELATIVE_MOVE = "RelativeMove" ABSOLUTE_MOVE = "AbsoluteMove" +GOTOPRESET_MOVE = "GotoPreset" SERVICE_PTZ = "ptz" @@ -99,10 +101,13 @@ SERVICE_PTZ_SCHEMA = vol.Schema( vol.Optional(ATTR_PAN): vol.In([DIR_LEFT, DIR_RIGHT]), vol.Optional(ATTR_TILT): vol.In([DIR_UP, DIR_DOWN]), vol.Optional(ATTR_ZOOM): vol.In([ZOOM_OUT, ZOOM_IN]), - ATTR_MOVE_MODE: vol.In([CONTINUOUS_MOVE, RELATIVE_MOVE, ABSOLUTE_MOVE]), + ATTR_MOVE_MODE: vol.In( + [CONTINUOUS_MOVE, RELATIVE_MOVE, ABSOLUTE_MOVE, GOTOPRESET_MOVE] + ), vol.Optional(ATTR_CONTINUOUS_DURATION, default=0.5): cv.small_float, vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float, vol.Optional(ATTR_SPEED, default=0.5): cv.small_float, + vol.Optional(ATTR_PRESET, default="0"): cv.string, } ) @@ -120,6 +125,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= speed = service.data[ATTR_SPEED] move_mode = service.data.get(ATTR_MOVE_MODE) continuous_duration = service.data[ATTR_CONTINUOUS_DURATION] + preset = service.data[ATTR_PRESET] all_cameras = hass.data[ONVIF_DATA][ENTITIES] entity_ids = await async_extract_entity_ids(hass, service) target_cameras = [] @@ -131,7 +137,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ] for camera in target_cameras: await camera.async_perform_ptz( - pan, tilt, zoom, distance, speed, move_mode, continuous_duration + pan, tilt, zoom, distance, speed, move_mode, continuous_duration, preset ) hass.services.async_register( @@ -435,7 +441,7 @@ class ONVIFHassCamera(Camera): _LOGGER.debug("Completed set up of the ONVIF camera component") async def async_perform_ptz( - self, pan, tilt, zoom, distance, speed, move_mode, continuous_duration + self, pan, tilt, zoom, distance, speed, move_mode, continuous_duration, preset ): """Perform a PTZ action on the camera.""" if self._ptz_service is None: @@ -447,13 +453,15 @@ class ONVIFHassCamera(Camera): tilt_val = distance * TILT_FACTOR.get(tilt, 0) zoom_val = distance * ZOOM_FACTOR.get(zoom, 0) speed_val = speed + preset_val = preset _LOGGER.debug( - "Calling %s PTZ | Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %4.2f", + "Calling %s PTZ | Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %4.2f | Preset = %s", move_mode, pan_val, tilt_val, zoom_val, speed_val, + preset_val, ) try: req = self._ptz_service.create_type(move_mode) @@ -489,6 +497,13 @@ class ONVIFHassCamera(Camera): "Zoom": {"x": speed_val}, } await self._ptz_service.AbsoluteMove(req) + elif move_mode == GOTOPRESET_MOVE: + req.PresetToken = preset_val + req.Speed = { + "PanTilt": {"x": speed_val, "y": speed_val}, + "Zoom": {"x": speed_val}, + } + await self._ptz_service.GotoPreset(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None diff --git a/homeassistant/components/onvif/services.yaml b/homeassistant/components/onvif/services.yaml index 8d14633cc9c..e5a8c9fce35 100644 --- a/homeassistant/components/onvif/services.yaml +++ b/homeassistant/components/onvif/services.yaml @@ -25,7 +25,10 @@ ptz: description: "Set ContinuousMove delay in seconds before stopping the move" default: 0.5 example: 0.5 + preset: + description: "PTZ preset profile token. Sets the preset profile token which is executed with GotoPreset" + example: "1" move_mode: - description: "PTZ moving mode. One of ContinuousMove, RelativeMove or AbsoluteMove" + description: "PTZ moving mode. One of ContinuousMove, RelativeMove, AbsoluteMove or GotoPreset" default: "RelativeMove" example: "ContinuousMove" From 175204a65e16a167b3f8ac4966ad40ca4ebab0f4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 24 Apr 2020 17:40:36 +0200 Subject: [PATCH 039/511] Updated frontend to 20200424.0 (#34645) --- 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 dfef5795599..f75605eaafc 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200422.0"], + "requirements": ["home-assistant-frontend==20200424.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c760440e63b..a6d72ca222f 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.1 -home-assistant-frontend==20200422.0 +home-assistant-frontend==20200424.0 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index dcc2a8ae24d..b9413514789 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -713,7 +713,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200422.0 +home-assistant-frontend==20200424.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c9543af1149..998e97dca90 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -291,7 +291,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200422.0 +home-assistant-frontend==20200424.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 71617e8e8b5b2fb473d605cda6422dde0c0bd47e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 24 Apr 2020 10:55:01 -0500 Subject: [PATCH 040/511] Fix failing vilfo test that was doing i/o (#34647) --- tests/components/vilfo/test_config_flow.py | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index d73d15df8dd..9176a9a1970 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -1,6 +1,5 @@ """Test the Vilfo Router config flow.""" -from unittest.mock import patch - +from asynctest.mock import patch import vilfo from homeassistant import config_entries, data_entry_flow, setup @@ -13,6 +12,7 @@ from tests.common import mock_coro async def test_form(hass): """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) + mock_mac = "FF-00-00-00-00-00" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -20,12 +20,11 @@ async def test_form(hass): assert result["errors"] == {} with patch("vilfo.Client.ping", return_value=None), patch( - "vilfo.Client.get_board_information", return_value=None, - ), patch( + "vilfo.Client.get_board_information", return_value=None + ), patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), patch( "homeassistant.components.vilfo.async_setup", return_value=mock_coro(True) ) as mock_setup, patch( - "homeassistant.components.vilfo.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.vilfo.async_setup_entry" ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -51,6 +50,8 @@ async def test_form_invalid_auth(hass): ) with patch("vilfo.Client.ping", return_value=None), patch( + "vilfo.Client.resolve_mac_address", return_value=None + ), patch( "vilfo.Client.get_board_information", side_effect=vilfo.exceptions.AuthenticationException, ): @@ -69,7 +70,9 @@ async def test_form_cannot_connect(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException): + with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), patch( + "vilfo.Client.resolve_mac_address" + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "testadmin.vilfo.com", "access_token": "test-token"}, @@ -78,7 +81,9 @@ async def test_form_cannot_connect(hass): assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["errors"] == {"base": "cannot_connect"} - with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException): + with patch("vilfo.Client.ping", side_effect=vilfo.exceptions.VilfoException), patch( + "vilfo.Client.resolve_mac_address" + ): result3 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "testadmin.vilfo.com", "access_token": "test-token"}, @@ -107,7 +112,7 @@ async def test_form_already_configured(hass): with patch("vilfo.Client.ping", return_value=None), patch( "vilfo.Client.get_board_information", return_value=None, - ): + ), patch("vilfo.Client.resolve_mac_address", return_value=None): first_flow_result2 = await hass.config_entries.flow.async_configure( first_flow_result1["flow_id"], {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, @@ -119,7 +124,7 @@ async def test_form_already_configured(hass): with patch("vilfo.Client.ping", return_value=None), patch( "vilfo.Client.get_board_information", return_value=None, - ): + ), patch("vilfo.Client.resolve_mac_address", return_value=None): second_flow_result2 = await hass.config_entries.flow.async_configure( second_flow_result1["flow_id"], {CONF_HOST: "testadmin.vilfo.com", CONF_ACCESS_TOKEN: "test-token"}, @@ -153,7 +158,7 @@ async def test_validate_input_returns_data(hass): with patch("vilfo.Client.ping", return_value=None), patch( "vilfo.Client.get_board_information", return_value=None - ): + ), patch("vilfo.Client.resolve_mac_address", return_value=None): result = await hass.components.vilfo.config_flow.validate_input( hass, data=mock_data ) From 4afa2a2651038e924dbeffc9398a64fec0865f6d Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Fri, 24 Apr 2020 19:30:45 +0300 Subject: [PATCH 041/511] Update dynalite library and minor changes (#34618) --- homeassistant/components/dynalite/__init__.py | 2 +- homeassistant/components/dynalite/bridge.py | 19 +++++++------------ homeassistant/components/dynalite/const.py | 1 - .../components/dynalite/convert_config.py | 6 +++--- .../components/dynalite/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/dynalite/test_bridge.py | 3 +-- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 2f2ed8f2fa2..78281f56f0f 100644 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -117,7 +117,7 @@ AREA_DATA_SCHEMA = vol.Schema( vol.All( { vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_TEMPLATE): cv.string, + vol.Optional(CONF_TEMPLATE): vol.In(DEFAULT_TEMPLATES), vol.Optional(CONF_FADE): vol.Coerce(float), vol.Optional(CONF_NO_DEFAULT): cv.boolean, vol.Optional(CONF_CHANNEL): CHANNEL_SCHEMA, diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index 09cf8e25a10..522061a85aa 100644 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,21 +1,16 @@ """Code to handle a Dynalite bridge.""" -from typing import TYPE_CHECKING, Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Optional -from dynalite_devices_lib.dynalite_devices import DynaliteDevices +from dynalite_devices_lib.dynalite_devices import DynaliteBaseDevice, DynaliteDevices from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import CONF_ALL, ENTITY_PLATFORMS, LOGGER +from .const import ENTITY_PLATFORMS, LOGGER from .convert_config import convert_config -if TYPE_CHECKING: # pragma: no cover - from dynalite_devices_lib.dynalite_devices import ( # pylint: disable=ungrouped-imports - DynaliteBaseDevice, - ) - class DynaliteBridge: """Manages a single Dynalite bridge.""" @@ -45,7 +40,7 @@ class DynaliteBridge: LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(convert_config(config)) - def update_signal(self, device: "DynaliteBaseDevice" = None) -> str: + def update_signal(self, device: Optional[DynaliteBaseDevice] = None) -> str: """Create signal to use to trigger entity update.""" if device: signal = f"dynalite-update-{self.host}-{device.unique_id}" @@ -54,9 +49,9 @@ class DynaliteBridge: return signal @callback - def update_device(self, device: "DynaliteBaseDevice") -> None: + def update_device(self, device: Optional[DynaliteBaseDevice] = None) -> None: """Call when a device or all devices should be updated.""" - if device == CONF_ALL: + if not device: # This is used to signal connection or disconnection, so all devices may become available or not. log_string = ( "Connected" if self.dynalite_devices.connected else "Disconnected" @@ -73,7 +68,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 ENTITY_PLATFORMS: platform_devices = [ diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index ade167e1b3e..82d66dba7ba 100644 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -14,7 +14,6 @@ CONF_ACTIVE = "active" ACTIVE_INIT = "init" ACTIVE_OFF = "off" ACTIVE_ON = "on" -CONF_ALL = "ALL" CONF_AREA = "area" CONF_AUTO_DISCOVER = "autodiscover" CONF_BRIDGES = "bridges" diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 03ece744d41..2e21d5edd9b 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -34,9 +34,9 @@ from .const import ( CONF_MAP = { CONF_ACTIVE: dyn_const.CONF_ACTIVE, - ACTIVE_INIT: dyn_const.CONF_ACTIVE_INIT, - ACTIVE_OFF: dyn_const.CONF_ACTIVE_OFF, - ACTIVE_ON: dyn_const.CONF_ACTIVE_ON, + ACTIVE_INIT: dyn_const.ACTIVE_INIT, + ACTIVE_OFF: dyn_const.ACTIVE_OFF, + ACTIVE_ON: dyn_const.ACTIVE_ON, CONF_AREA: dyn_const.CONF_AREA, CONF_AUTO_DISCOVER: dyn_const.CONF_AUTO_DISCOVER, CONF_CHANNEL: dyn_const.CONF_CHANNEL, diff --git a/homeassistant/components/dynalite/manifest.json b/homeassistant/components/dynalite/manifest.json index 39f72f57b06..581110ba583 100644 --- a/homeassistant/components/dynalite/manifest.json +++ b/homeassistant/components/dynalite/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/dynalite", "codeowners": ["@ziv1234"], - "requirements": ["dynalite_devices==0.1.39"] + "requirements": ["dynalite_devices==0.1.40"] } diff --git a/requirements_all.txt b/requirements_all.txt index b9413514789..6a9dba029a7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -481,7 +481,7 @@ dsmr_parser==0.18 dweepy==0.3.0 # homeassistant.components.dynalite -dynalite_devices==0.1.39 +dynalite_devices==0.1.40 # homeassistant.components.rainforest_eagle eagle200_reader==0.2.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 998e97dca90..fc4d2fd34a1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -197,7 +197,7 @@ doorbirdpy==2.0.8 dsmr_parser==0.18 # homeassistant.components.dynalite -dynalite_devices==0.1.39 +dynalite_devices==0.1.40 # homeassistant.components.ee_brightbox eebrightbox==0.0.4 diff --git a/tests/components/dynalite/test_bridge.py b/tests/components/dynalite/test_bridge.py index 938bc09f59a..0b093fd5c3e 100644 --- a/tests/components/dynalite/test_bridge.py +++ b/tests/components/dynalite/test_bridge.py @@ -1,7 +1,6 @@ """Test Dynalite bridge.""" from asynctest import CoroutineMock, Mock, patch -from dynalite_devices_lib.const import CONF_ALL from homeassistant.components import dynalite from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -29,7 +28,7 @@ async def test_update_device(hass): async_dispatcher_connect( hass, f"dynalite-update-{host}-{device.unique_id}", specific_func ) - update_device_func(CONF_ALL) + update_device_func() await hass.async_block_till_done() wide_func.assert_called_once() specific_func.assert_not_called() From 6404882ec4883d0293efa83df20874e7983c7988 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 09:31:56 -0700 Subject: [PATCH 042/511] Allow flows to know if user is in advanced mode (#34629) --- homeassistant/data_entry_flow.py | 10 ++++++++++ homeassistant/helpers/data_entry_flow.py | 14 ++++++++++++-- tests/components/config/test_config_entries.py | 13 ++++++++++--- tests/test_data_entry_flow.py | 1 - 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py index 51f083b7eeb..340ec671eda 100644 --- a/homeassistant/data_entry_flow.py +++ b/homeassistant/data_entry_flow.py @@ -253,6 +253,16 @@ class FlowHandler: # Set by developer VERSION = 1 + @property + def source(self) -> Optional[str]: + """Source that initialized the flow.""" + return self.context.get("source", None) + + @property + def show_advanced_options(self) -> bool: + """If we should show advanced options.""" + return self.context.get("show_advanced_options", False) + @callback def async_show_form( self, diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 951b6d4c748..fe6420750c6 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -49,7 +49,13 @@ class FlowManagerIndexView(_BaseFlowManagerView): """View to create config flows.""" @RequestDataValidator( - vol.Schema({vol.Required("handler"): vol.Any(str, list)}, extra=vol.ALLOW_EXTRA) + vol.Schema( + { + vol.Required("handler"): vol.Any(str, list), + vol.Optional("show_advanced_options", default=False): cv.boolean, + }, + extra=vol.ALLOW_EXTRA, + ) ) async def post(self, request, data): """Handle a POST request.""" @@ -60,7 +66,11 @@ class FlowManagerIndexView(_BaseFlowManagerView): try: result = await self._flow_mgr.async_init( - handler, context={"source": config_entries.SOURCE_USER} + handler, + context={ + "source": config_entries.SOURCE_USER, + "show_advanced_options": data["show_advanced_options"], + }, ) except data_entry_flow.UnknownHandler: return self.json_message("Invalid handler specified", HTTP_NOT_FOUND) diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 9eb9a741da0..358f952e191 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -141,13 +141,17 @@ async def test_initialize_flow(hass, client): return self.async_show_form( step_id="user", data_schema=schema, - description_placeholders={"url": "https://example.com"}, + description_placeholders={ + "url": "https://example.com", + "show_advanced_options": self.show_advanced_options, + }, errors={"username": "Should be unique."}, ) with patch.dict(HANDLERS, {"test": TestFlow}): resp = await client.post( - "/api/config/config_entries/flow", json={"handler": "test"} + "/api/config/config_entries/flow", + json={"handler": "test", "show_advanced_options": True}, ) assert resp.status == 200 @@ -163,7 +167,10 @@ async def test_initialize_flow(hass, client): {"name": "username", "required": True, "type": "string"}, {"name": "password", "required": True, "type": "string"}, ], - "description_placeholders": {"url": "https://example.com"}, + "description_placeholders": { + "url": "https://example.com", + "show_advanced_options": True, + }, "errors": {"username": "Should be unique."}, } diff --git a/tests/test_data_entry_flow.py b/tests/test_data_entry_flow.py index 664304c9ef6..ad63dae6ee6 100644 --- a/tests/test_data_entry_flow.py +++ b/tests/test_data_entry_flow.py @@ -26,7 +26,6 @@ def manager(): flow = handler() flow.init_step = context.get("init_step", "init") - flow.source = context.get("source") return flow async def async_finish_flow(self, flow, result): From c93c6a66e865d68698dd9c6bd0bddd42038cb0bd Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 24 Apr 2020 18:40:23 +0200 Subject: [PATCH 043/511] Add NOT condition helper (#34624) --- .../components/automation/__init__.py | 1 + homeassistant/helpers/condition.py | 26 +++++++ homeassistant/helpers/config_validation.py | 12 ++++ tests/helpers/test_condition.py | 67 +++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 76fe619cc1c..34a83f3ed59 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -54,6 +54,7 @@ CONF_SKIP_CONDITION = "skip_condition" CONDITION_USE_TRIGGER_VALUES = "use_trigger_values" CONDITION_TYPE_AND = "and" +CONDITION_TYPE_NOT = "not" CONDITION_TYPE_OR = "or" DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 363d33b14ea..61704e2d23a 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -137,6 +137,32 @@ async def async_or_from_config( return if_or_condition +async def async_not_from_config( + hass: HomeAssistant, config: ConfigType, config_validation: bool = True +) -> ConditionCheckerType: + """Create multi condition matcher using 'NOT'.""" + if config_validation: + config = cv.NOT_CONDITION_SCHEMA(config) + checks = [ + await async_from_config(hass, entry, False) for entry in config["conditions"] + ] + + def if_not_condition( + hass: HomeAssistant, variables: TemplateVarsType = None + ) -> bool: + """Test not condition.""" + try: + for check in checks: + if check(hass, variables): + return False + except Exception as ex: # pylint: disable=broad-except + _LOGGER.warning("Error during not-condition: %s", ex) + + return True + + return if_not_condition + + def numeric_state( hass: HomeAssistant, entity: Union[None, str, State], diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 7bb3223f3d7..01f737b47da 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -924,6 +924,17 @@ OR_CONDITION_SCHEMA = vol.Schema( } ) +NOT_CONDITION_SCHEMA = vol.Schema( + { + vol.Required(CONF_CONDITION): "not", + vol.Required("conditions"): vol.All( + ensure_list, + # pylint: disable=unnecessary-lambda + [lambda value: CONDITION_SCHEMA(value)], + ), + } +) + DEVICE_CONDITION_BASE_SCHEMA = vol.Schema( { vol.Required(CONF_CONDITION): "device", @@ -945,6 +956,7 @@ CONDITION_SCHEMA: vol.Schema = key_value_schemas( "zone": ZONE_CONDITION_SCHEMA, "and": AND_CONDITION_SCHEMA, "or": OR_CONDITION_SCHEMA, + "not": NOT_CONDITION_SCHEMA, "device": DEVICE_CONDITION_SCHEMA, }, ) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 4e8e59aad78..cadce628c10 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -127,6 +127,73 @@ async def test_or_condition_with_template(hass): assert test(hass) +async def test_not_condition(hass): + """Test the 'not' condition.""" + test = await condition.async_from_config( + hass, + { + "condition": "not", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "below": 50, + }, + ], + }, + ) + + hass.states.async_set("sensor.temperature", 101) + assert test(hass) + + hass.states.async_set("sensor.temperature", 50) + assert test(hass) + + hass.states.async_set("sensor.temperature", 49) + assert not test(hass) + + hass.states.async_set("sensor.temperature", 100) + assert not test(hass) + + +async def test_not_condition_with_template(hass): + """Test the 'or' condition.""" + test = await condition.async_from_config( + hass, + { + "condition": "not", + "conditions": [ + { + "condition": "template", + "value_template": '{{ states.sensor.temperature.state == "100" }}', + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature", + "below": 50, + }, + ], + }, + ) + + hass.states.async_set("sensor.temperature", 101) + assert test(hass) + + hass.states.async_set("sensor.temperature", 50) + assert test(hass) + + hass.states.async_set("sensor.temperature", 49) + assert not test(hass) + + hass.states.async_set("sensor.temperature", 100) + assert not test(hass) + + async def test_time_window(hass): """Test time condition windows.""" sixam = dt.parse_time("06:00:00") From a2fab264c4eb3ed2ea3d717a6b6073752dc76249 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 09:52:23 -0700 Subject: [PATCH 044/511] Fix identifying Plex schema when used in packages (#34651) --- homeassistant/config.py | 25 +++++++++++++++++++------ tests/test_config.py | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index b9fdcb085ef..56bbe76a045 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -559,12 +559,23 @@ def _log_pkg_error(package: str, component: str, config: Dict, message: str) -> def _identify_config_schema(module: ModuleType) -> Optional[str]: """Extract the schema and identify list or dict based.""" - try: - key = next(k for k in module.CONFIG_SCHEMA.schema if k == module.DOMAIN) # type: ignore - except (AttributeError, StopIteration): - return None + schema = module.CONFIG_SCHEMA.schema # type: ignore - schema = module.CONFIG_SCHEMA.schema[key] # type: ignore + if isinstance(schema, vol.All): + for subschema in schema.validators: + if isinstance(subschema, dict): + schema = subschema + break + else: + return None + + try: + key = next(k for k in schema if k == module.DOMAIN) # type: ignore + except (TypeError, AttributeError, StopIteration): + return None + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected error identifying config schema") + return None if hasattr(key, "default") and not isinstance( key.default, vol.schema_builder.Undefined @@ -581,7 +592,9 @@ def _identify_config_schema(module: ModuleType) -> Optional[str]: return None - t_schema = str(schema) + domain_schema = schema[key] + + t_schema = str(domain_schema) if t_schema.startswith("{") or "schema_with_slug_keys" in t_schema: return "dict" if t_schema.startswith(("[", "All( Date: Fri, 24 Apr 2020 12:09:38 -0500 Subject: [PATCH 045/511] Restore Expected Behavior of Sonarr Upcoming Sensor (#34408) --- homeassistant/components/sonarr/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index c0350353b4c..65513db3571 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -206,7 +206,8 @@ class SonarrSensor(Entity): def update(self): """Update the data for the sensor.""" - start = dt_util.utcnow().replace(microsecond=0) + local = dt_util.start_of_local_day().replace(microsecond=0) + start = dt_util.as_utc(local) end = start + timedelta(days=self.days) try: res = requests.get( From 86d3321d5978036200a476cf3ada9b915b78f4e6 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 24 Apr 2020 11:11:17 -0600 Subject: [PATCH 046/511] Store integration type in AirVisual config entry (#34621) --- .../components/airvisual/__init__.py | 28 ++++++++++++++++++- .../components/airvisual/config_flow.py | 7 +++-- homeassistant/components/airvisual/const.py | 1 + .../components/airvisual/test_config_flow.py | 10 +++++-- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 7f81b906237..73c39a450b8 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -30,6 +30,7 @@ from .const import ( CONF_CITY, CONF_COUNTRY, CONF_GEOGRAPHIES, + CONF_INTEGRATION_TYPE, DATA_CLIENT, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY, @@ -120,7 +121,7 @@ async def async_setup(hass, config): @callback def _standardize_geography_config_entry(hass, config_entry): - """Ensure that geography observables have appropriate properties.""" + """Ensure that geography config entries have appropriate properties.""" entry_updates = {} if not config_entry.unique_id: @@ -129,6 +130,30 @@ 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, + } + + if not entry_updates: + return + + hass.config_entries.async_update_entry(config_entry, **entry_updates) + + +@callback +def _standardize_node_pro_config_entry(hass, config_entry): + """Ensure that Node/Pro config entries have appropriate properties.""" + entry_updates = {} + + 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_NODE_PRO, + } if not entry_updates: return @@ -151,6 +176,7 @@ async def async_setup_entry(hass, config_entry): # Only geography-based entries have options: config_entry.add_update_listener(async_update_options) else: + _standardize_node_pro_config_entry(hass, config_entry) airvisual = AirVisualNodeProData(hass, Client(websession), config_entry) await airvisual.async_update() diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index ef15f8dcc99..22a8c776027 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -20,6 +20,7 @@ from homeassistant.helpers import aiohttp_client, config_validation as cv from . import async_get_geography_id from .const import ( # pylint: disable=unused-import CONF_GEOGRAPHIES, + CONF_INTEGRATION_TYPE, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO, @@ -123,7 +124,8 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): checked_keys.add(user_input[CONF_API_KEY]) return self.async_create_entry( - title=f"Cloud API ({geo_id})", data=user_input + title=f"Cloud API ({geo_id})", + data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY}, ) async def async_step_import(self, import_config): @@ -155,7 +157,8 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_create_entry( - title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", data=user_input + title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", + data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO}, ) async def async_step_user(self, user_input=None): diff --git a/homeassistant/components/airvisual/const.py b/homeassistant/components/airvisual/const.py index 482c4191480..0e0e62a9b0c 100644 --- a/homeassistant/components/airvisual/const.py +++ b/homeassistant/components/airvisual/const.py @@ -10,6 +10,7 @@ INTEGRATION_TYPE_NODE_PRO = "AirVisual Node/Pro" CONF_CITY = "city" CONF_COUNTRY = "country" CONF_GEOGRAPHIES = "geographies" +CONF_INTEGRATION_TYPE = "integration_type" DATA_CLIENT = "client" diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index 57852969d71..9127e6b2780 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -5,6 +5,7 @@ from pyairvisual.errors import InvalidKeyError, NodeProError from homeassistant import data_entry_flow from homeassistant.components.airvisual import ( CONF_GEOGRAPHIES, + CONF_INTEGRATION_TYPE, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO, @@ -93,8 +94,8 @@ async def test_node_pro_error(hass): assert result["errors"] == {CONF_IP_ADDRESS: "unable_to_connect"} -async def test_migration_1_2(hass): - """Test migrating from version 1 to version 2.""" +async def test_migration(hass): + """Test migrating from version 1 to the current version.""" conf = { CONF_API_KEY: "abcde12345", CONF_GEOGRAPHIES: [ @@ -123,6 +124,7 @@ async def test_migration_1_2(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, } assert config_entries[1].unique_id == "35.48847, 137.5263065" @@ -131,6 +133,7 @@ async def test_migration_1_2(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 35.48847, CONF_LONGITUDE: 137.5263065, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, } @@ -186,6 +189,7 @@ async def test_step_geography(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, } @@ -207,6 +211,7 @@ async def test_step_node_pro(hass): assert result["data"] == { CONF_IP_ADDRESS: "192.168.1.100", CONF_PASSWORD: "my_password", + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO, } @@ -231,6 +236,7 @@ async def test_step_import(hass): CONF_API_KEY: "abcde12345", CONF_LATITUDE: 51.528308, CONF_LONGITUDE: -0.3817765, + CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_GEOGRAPHY, } From 9994554c5621dfafb4cf72ed57a9db2d31adcb8d Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 24 Apr 2020 16:29:13 -0400 Subject: [PATCH 047/511] Update ZHA dependency (#34661) Bump up zigpy-deconz --- 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 82023eb9e15..e49f4f1407a 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows-homeassistant==0.15.2", "zha-quirks==0.0.38", "zigpy-cc==0.3.1", - "zigpy-deconz==0.8.0", + "zigpy-deconz==0.8.1", "zigpy-homeassistant==0.19.0", "zigpy-xbee-homeassistant==0.11.0", "zigpy-zigate==0.5.1" diff --git a/requirements_all.txt b/requirements_all.txt index 6a9dba029a7..c51b24ab0b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2199,7 +2199,7 @@ ziggo-mediabox-xl==1.1.0 zigpy-cc==0.3.1 # homeassistant.components.zha -zigpy-deconz==0.8.0 +zigpy-deconz==0.8.1 # homeassistant.components.zha zigpy-homeassistant==0.19.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc4d2fd34a1..aba541eb7a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -836,7 +836,7 @@ zha-quirks==0.0.38 zigpy-cc==0.3.1 # homeassistant.components.zha -zigpy-deconz==0.8.0 +zigpy-deconz==0.8.1 # homeassistant.components.zha zigpy-homeassistant==0.19.0 From b741199ed3b7b3f257443f37245d6ad2c70ad0d4 Mon Sep 17 00:00:00 2001 From: Quentame Date: Fri, 24 Apr 2020 23:10:57 +0200 Subject: [PATCH 048/511] Log config flow errors (#34665) --- homeassistant/components/synology_dsm/config_flow.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 025b2959026..4b09e516451 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -120,11 +120,14 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors[CONF_OTP_CODE] = "otp_failed" user_input[CONF_OTP_CODE] = None return await self.async_step_2sa(user_input, errors) - except SynologyDSMLoginInvalidException: + except SynologyDSMLoginInvalidException as ex: + _LOGGER.error(ex) errors[CONF_USERNAME] = "login" - except SynologyDSMRequestException: + except SynologyDSMRequestException as ex: + _LOGGER.error(ex) errors[CONF_HOST] = "connection" - except SynologyDSMException: + except SynologyDSMException as ex: + _LOGGER.error(ex) errors["base"] = "unknown" except InvalidData: errors["base"] = "missing_data" From 77443b3d093fc27c786c3f651a425c0d5f0f1a53 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 14:13:39 -0700 Subject: [PATCH 049/511] Add Home Assistant Started event (#34657) --- .../components/automation/__init__.py | 16 +++++---- .../components/automation/homeassistant.py | 4 +-- .../components/cloud/google_config.py | 27 ++++++++++++-- homeassistant/const.py | 1 + homeassistant/core.py | 2 ++ .../automation/test_homeassistant.py | 2 ++ tests/components/automation/test_init.py | 5 +-- tests/components/cloud/test_google_config.py | 36 +++++++++++++------ 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 34a83f3ed59..3a5b3966d40 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_ZONE, EVENT_AUTOMATION_TRIGGERED, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF, @@ -409,7 +409,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): # HomeAssistant is starting up if self.hass.state != CoreState.not_running: - self._async_detach_triggers = await self._async_attach_triggers() + self._async_detach_triggers = await self._async_attach_triggers(False) self.async_write_ha_state() return @@ -419,10 +419,10 @@ class AutomationEntity(ToggleEntity, RestoreEntity): if not self._is_enabled or self._async_detach_triggers is not None: return - self._async_detach_triggers = await self._async_attach_triggers() + self._async_detach_triggers = await self._async_attach_triggers(True) self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, async_enable_automation + EVENT_HOMEASSISTANT_STARTED, async_enable_automation ) self.async_write_ha_state() @@ -439,15 +439,17 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self.async_write_ha_state() - async def _async_attach_triggers(self): + async def _async_attach_triggers( + self, home_assistant_start: bool + ) -> Optional[Callable[[], None]]: """Set up the triggers.""" removes = [] - info = {"name": self._name} + info = {"name": self._name, "home_assistant_start": home_assistant_start} for conf in self._trigger_config: platform = importlib.import_module(f".{conf[CONF_PLATFORM]}", __name__) - remove = await platform.async_attach_trigger( + remove = await platform.async_attach_trigger( # type: ignore self.hass, conf, self.async_trigger, info ) diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 743b169c86c..91b67e28c7c 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.const import CONF_EVENT, CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import CoreState, callback +from homeassistant.core import callback # mypy: allow-untyped-defs @@ -40,7 +40,7 @@ async def async_attach_trigger(hass, config, action, automation_info): # Automation are enabled while hass is starting up, fire right away # Check state because a config reload shouldn't trigger it. - if hass.state == CoreState.starting: + if automation_info["home_assistant_start"]: hass.async_run_job( action({"trigger": {"platform": "homeassistant", "event": event}}) ) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index bb6dcaa2fe2..9b94b77ca45 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -6,7 +6,12 @@ from hass_nabucasa import 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, HTTP_OK +from homeassistant.const import ( + CLOUD_NEVER_EXPOSED_ENTITIES, + EVENT_HOMEASSISTANT_STARTED, + HTTP_OK, +) +from homeassistant.core import CoreState, callback from homeassistant.helpers import entity_registry from .const import ( @@ -32,6 +37,7 @@ class CloudGoogleConfig(AbstractConfig): self._cloud = cloud self._cur_entity_prefs = self._prefs.google_entity_configs self._sync_entities_lock = asyncio.Lock() + self._sync_on_started = False @property def enabled(self): @@ -169,6 +175,21 @@ class CloudGoogleConfig(AbstractConfig): entity_id = event.data["entity_id"] - # Schedule a sync if a change was made to an entity that Google knows about - if self._should_expose_entity_id(entity_id): + if not self._should_expose_entity_id(entity_id): + return + + if self.hass.state == CoreState.running: self.async_schedule_google_sync_all() + return + + if self._sync_on_started: + return + + self._sync_on_started = True + + @callback + 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) diff --git a/homeassistant/const.py b/homeassistant/const.py index 442a4aa7fc2..2b975f52e18 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -184,6 +184,7 @@ EVENT_COMPONENT_LOADED = "component_loaded" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" +EVENT_HOMEASSISTANT_STARTED = "homeassistant_started" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_HOMEASSISTANT_FINAL_WRITE = "homeassistant_final_write" EVENT_LOGBOOK_ENTRY = "logbook_entry" diff --git a/homeassistant/core.py b/homeassistant/core.py index c8c5fc4d499..c799656df89 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -50,6 +50,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_FINAL_WRITE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, @@ -279,6 +280,7 @@ class HomeAssistant: self.state = CoreState.running _async_create_timer(self) + self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) def add_job(self, target: Callable[..., Any], *args: Any) -> None: """Add job to the executor pool. diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py index ee4293effe3..a0985e54976 100644 --- a/tests/components/automation/test_homeassistant.py +++ b/tests/components/automation/test_homeassistant.py @@ -25,6 +25,7 @@ async def test_if_fires_on_hass_start(hass): assert len(calls) == 0 await hass.async_start() + await hass.async_block_till_done() assert automation.is_on(hass, "automation.hello") assert len(calls) == 1 @@ -61,6 +62,7 @@ async def test_if_fires_on_hass_shutdown(hass): await hass.async_start() assert automation.is_on(hass, "automation.hello") + await hass.async_block_till_done() assert len(calls) == 0 with patch.object(hass.loop, "stop"): diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index c27a0262a4e..cd4a01e9a28 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -10,7 +10,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, EVENT_AUTOMATION_TRIGGERED, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, STATE_OFF, STATE_ON, ) @@ -700,6 +700,7 @@ async def test_initial_value_on(hass): assert automation.is_on(hass, "automation.hello") await hass.async_start() + await hass.async_block_till_done() hass.bus.async_fire("test_event") await hass.async_block_till_done() assert len(calls) == 1 @@ -822,7 +823,7 @@ async def test_automation_not_trigger_on_bootstrap(hass): await hass.async_block_till_done() assert len(calls) == 0 - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert automation.is_on(hass, "automation.hello") diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 2474851cce8..b08b950a590 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -6,7 +6,8 @@ from asynctest import patch from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig from homeassistant.components.google_assistant import helpers as ga_helpers -from homeassistant.const import HTTP_NOT_FOUND +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, HTTP_NOT_FOUND +from homeassistant.core import CoreState from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.util.dt import utcnow @@ -25,9 +26,7 @@ async def test_google_update_report_state(hass, cloud_prefs): await config.async_initialize() await config.async_connect_agent_user("mock-user-id") - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch( + with patch.object(config, "async_sync_entities") as mock_sync, patch( "homeassistant.components.google_assistant.report_state.async_enable_report_state" ) as mock_report_state: await cloud_prefs.async_update(google_report_state=True) @@ -67,9 +66,9 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs): await config.async_initialize() await config.async_connect_agent_user("mock-user-id") - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + with patch.object(config, "async_sync_entities") as mock_sync, patch.object( + ga_helpers, "SYNC_DELAY", 0 + ): await cloud_prefs.async_update_google_entity_config( entity_id="light.kitchen", should_expose=True ) @@ -79,9 +78,9 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs): assert len(mock_sync.mock_calls) == 1 - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + with patch.object(config, "async_sync_entities") as mock_sync, patch.object( + ga_helpers, "SYNC_DELAY", 0 + ): await cloud_prefs.async_update_google_entity_config( entity_id="light.kitchen", should_expose=False ) @@ -107,7 +106,7 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): await config.async_connect_agent_user("mock-user-id") with patch.object( - config, "async_schedule_google_sync_all", side_effect=mock_coro + config, "async_schedule_google_sync_all" ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): # Created entity hass.bus.async_fire( @@ -148,3 +147,18 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 3 + + # When hass is not started yet we wait till started + hass.state = CoreState.starting + hass.bus.async_fire( + EVENT_ENTITY_REGISTRY_UPDATED, + {"action": "create", "entity_id": "light.kitchen"}, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 3 + + with patch.object(config, "async_sync_entities_all") as mock_sync: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + assert len(mock_sync.mock_calls) == 1 From 40d3d640274b4a994c0d356d8c14882ddf6b3b44 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 24 Apr 2020 17:18:58 -0400 Subject: [PATCH 050/511] Fix recording duration flag (#34648) --- homeassistant/components/stream/manifest.json | 2 +- homeassistant/components/stream/recorder.py | 12 +++++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 873d76cf189..2cc60938a8d 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -2,7 +2,7 @@ "domain": "stream", "name": "Stream", "documentation": "https://www.home-assistant.io/integrations/stream", - "requirements": ["av==6.1.2"], + "requirements": ["av==7.0.1"], "dependencies": ["http"], "codeowners": ["@hunterjm"], "quality_scale": "internal" diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 1dd90b8b804..c28c73c64ac 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -17,7 +17,8 @@ def async_setup_recorder(hass): def recorder_save_worker(file_out: str, segments: List[Segment]): """Handle saving stream.""" - output = av.open(file_out, "w", options={"movflags": "frag_keyframe"}) + first_pts = None + output = av.open(file_out, "w") output_v = None for segment in segments: @@ -29,13 +30,22 @@ def recorder_save_worker(file_out: str, segments: List[Segment]): # Add output streams if not output_v: output_v = output.add_stream(template=source_v) + context = output_v.codec_context + context.flags |= "GLOBAL_HEADER" # Remux video for packet in source.demux(source_v): if packet is not None and packet.dts is not None: + if first_pts is None: + first_pts = packet.pts + + packet.pts -= first_pts + packet.dts -= first_pts packet.stream = output_v output.mux(packet) + source.close() + output.close() diff --git a/requirements_all.txt b/requirements_all.txt index c51b24ab0b9..cc6f15be0db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -282,7 +282,7 @@ atenpdu==0.3.0 aurorapy==0.2.6 # homeassistant.components.stream -av==6.1.2 +av==7.0.1 # homeassistant.components.avea avea==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aba541eb7a5..620adff681a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -126,7 +126,7 @@ arcam-fmj==0.4.3 async-upnp-client==0.14.13 # homeassistant.components.stream -av==6.1.2 +av==7.0.1 # homeassistant.components.axis axis==25 From 9a4a83cb913a43c7b677cc60cc1f3e5f8ffc1b19 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 15:09:45 -0700 Subject: [PATCH 051/511] Fix py38 tests (#34658) --- .../google_assistant/test_smart_home.py | 18 +-- .../components/google_assistant/test_trait.py | 7 +- tests/components/ps4/conftest.py | 22 +++ tests/components/ps4/test_config_flow.py | 7 +- tests/components/ps4/test_init.py | 14 +- tests/components/ps4/test_media_player.py | 137 ++++++------------ tests/components/zeroconf/test_init.py | 11 +- 7 files changed, 92 insertions(+), 124 deletions(-) create mode 100644 tests/components/ps4/conftest.py diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 42002d62906..d3c9da94f04 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1,6 +1,5 @@ """Test Google Smart Home.""" -from unittest.mock import Mock, patch - +from asynctest import Mock, patch import pytest from homeassistant.components import camera @@ -29,12 +28,7 @@ from homeassistant.setup import async_setup_component from . import BASIC_CONFIG, MockConfig -from tests.common import ( - mock_area_registry, - mock_coro, - mock_device_registry, - mock_registry, -) +from tests.common import mock_area_registry, mock_device_registry, mock_registry REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" @@ -262,6 +256,8 @@ async def test_query_message(hass): }, } + await hass.async_block_till_done() + assert len(events) == 4 assert events[0].event_type == EVENT_QUERY_RECEIVED assert events[0].data == { @@ -788,9 +784,7 @@ async def test_query_disconnect(hass): config = MockConfig(hass=hass) config.async_enable_report_state() assert config._unsub_report_state is not None - with patch.object( - config, "async_disconnect_agent_user", side_effect=mock_coro - ) as mock_disconnect: + with patch.object(config, "async_disconnect_agent_user") as mock_disconnect: result = await sh.async_handle_message( hass, config, @@ -811,7 +805,7 @@ async def test_trait_execute_adding_query_data(hass): with patch( "homeassistant.components.camera.async_request_stream", - return_value=mock_coro("/api/streams/bla"), + return_value="/api/streams/bla", ): 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 a2b8f2e9ea7..fed084586cc 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,7 +1,8 @@ """Tests for the Google Assistant traits.""" import logging -from unittest.mock import Mock, patch +from unittest.mock import Mock +from asynctest import patch import pytest from homeassistant.components import ( @@ -45,7 +46,7 @@ from homeassistant.util import color from . import BASIC_CONFIG, MockConfig -from tests.common import async_mock_service, mock_coro +from tests.common import async_mock_service _LOGGER = logging.getLogger(__name__) @@ -117,7 +118,7 @@ async def test_camera_stream(hass): with patch( "homeassistant.components.camera.async_request_stream", - return_value=mock_coro("/api/streams/bla"), + return_value="/api/streams/bla", ): await trt.execute(trait.COMMAND_GET_CAMERA_STREAM, BASIC_DATA, {}, {}) diff --git a/tests/components/ps4/conftest.py b/tests/components/ps4/conftest.py new file mode 100644 index 00000000000..e945af3220d --- /dev/null +++ b/tests/components/ps4/conftest.py @@ -0,0 +1,22 @@ +"""Test configuration for PS4.""" +from asynctest import patch +import pytest + + +@pytest.fixture +def patch_load_json(): + """Prevent load JSON being used.""" + with patch("homeassistant.components.ps4.load_json", return_value={}) as mock_load: + yield mock_load + + +@pytest.fixture +def patch_save_json(): + """Prevent save JSON being used.""" + with patch("homeassistant.components.ps4.save_json") as mock_save: + yield mock_save + + +@pytest.fixture(autouse=True) +def patch_io(patch_load_json, patch_save_json): + """Prevent PS4 doing I/O.""" diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 7c021199952..06f10da4f2b 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -1,6 +1,5 @@ """Define tests for the PlayStation 4 config flow.""" -from unittest.mock import patch - +from asynctest import patch from pyps4_2ndscreen.errors import CredentialTimeout from homeassistant import data_entry_flow @@ -21,7 +20,7 @@ from homeassistant.const import ( ) from homeassistant.util import location -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry MOCK_TITLE = "PlayStation 4" MOCK_CODE = 12345678 @@ -313,7 +312,7 @@ async def test_0_pin(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ), patch( "homeassistant.components.ps4.config_flow.location.async_detect_location_info", - return_value=mock_coro(MOCK_LOCATION), + return_value=MOCK_LOCATION, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], MOCK_AUTO diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 453202b6c67..68179ad54c2 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -1,5 +1,5 @@ """Tests for the PS4 Integration.""" -from unittest.mock import MagicMock, patch +from asynctest import MagicMock, patch from homeassistant import config_entries, data_entry_flow from homeassistant.components import ps4 @@ -29,7 +29,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.util import location -from tests.common import MockConfigEntry, mock_coro, mock_registry +from tests.common import MockConfigEntry, mock_registry MOCK_HOST = "192.168.0.1" MOCK_NAME = "test_ps4" @@ -119,8 +119,8 @@ async def test_creating_entry_sets_up_media_player(hass): mock_flow = "homeassistant.components.ps4.PlayStation4FlowHandler.async_step_user" with patch( "homeassistant.components.ps4.media_player.async_setup_entry", - return_value=mock_coro(True), - ) as mock_setup, patch(mock_flow, return_value=mock_coro(MOCK_FLOW_RESULT)): + return_value=True, + ) as mock_setup, patch(mock_flow, return_value=MOCK_FLOW_RESULT): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -152,10 +152,10 @@ async def test_config_flow_entry_migrate(hass): with patch( "homeassistant.util.location.async_detect_location_info", - return_value=mock_coro(MOCK_LOCATION), + return_value=MOCK_LOCATION, ), patch( "homeassistant.helpers.entity_registry.async_get_registry", - return_value=mock_coro(mock_e_registry), + return_value=mock_e_registry, ): await ps4.async_migrate_entry(hass, mock_entry) @@ -281,7 +281,7 @@ async def test_send_command(hass): assert mock_entity.entity_id == f"media_player.{MOCK_NAME}" # Test that all commands call service function. - with patch(mock_func, return_value=mock_coro(True)) as mock_service: + with patch(mock_func, return_value=True) as mock_service: for mock_command in COMMANDS: await hass.services.async_call( DOMAIN, diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index 3bd75c40bed..b7725795f5c 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -1,6 +1,5 @@ """Tests for the PS4 media player platform.""" -from unittest.mock import MagicMock, patch - +from asynctest import MagicMock, patch from pyps4_2ndscreen.credential import get_ddp_message from homeassistant.components import ps4 @@ -35,7 +34,7 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_coro, mock_device_registry, mock_registry +from tests.common import MockConfigEntry, mock_device_registry, mock_registry MOCK_CREDS = "123412341234abcd12341234abcd12341234abcd12341234abcd12341234abcd" MOCK_NAME = "ha_ps4_name" @@ -123,7 +122,6 @@ MOCK_DATA = {CONF_TOKEN: MOCK_CREDS, "devices": [MOCK_DEVICE]} MOCK_CONFIG = MockConfigEntry(domain=DOMAIN, data=MOCK_DATA, entry_id=MOCK_ENTRY_ID) MOCK_LOAD = "homeassistant.components.ps4.media_player.load_games" -MOCK_SAVE = "homeassistant.components.ps4.save_json" async def setup_mock_component(hass, entry=None): @@ -137,9 +135,7 @@ async def setup_mock_component(hass, entry=None): mock_entry.add_to_hass(hass) - # Don't use an actual file. - with patch(MOCK_LOAD, return_value={}), patch(MOCK_SAVE, side_effect=MagicMock()): - await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) await hass.async_block_till_done() @@ -150,7 +146,7 @@ async def setup_mock_component(hass, entry=None): return mock_entity_id -async def mock_ddp_response(hass, mock_status_data, games=None): +async def mock_ddp_response(hass, mock_status_data): """Mock raw UDP response from device.""" mock_protocol = hass.data[PS4_DATA].protocol @@ -159,14 +155,8 @@ async def mock_ddp_response(hass, mock_status_data, games=None): mock_status_header = f"{mock_code} {mock_status}" mock_response = get_ddp_message(mock_status_header, mock_status_data).encode() - if games is None: - games = {} - - with patch(MOCK_LOAD, return_value=games), patch( - MOCK_SAVE, side_effect=MagicMock() - ): - mock_protocol.datagram_received(mock_response, (MOCK_HOST, MOCK_RANDOM_PORT)) - await hass.async_block_till_done() + mock_protocol.datagram_received(mock_response, (MOCK_HOST, MOCK_RANDOM_PORT)) + await hass.async_block_till_done() async def test_media_player_is_setup_correctly_with_entry(hass): @@ -187,8 +177,7 @@ async def test_state_standby_is_set(hass): """Test that state is set to standby.""" mock_entity_id = await setup_mock_component(hass) - with patch(MOCK_SAVE, side_effect=MagicMock()): - await mock_ddp_response(hass, MOCK_STATUS_STANDBY) + await mock_ddp_response(hass, MOCK_STATUS_STANDBY) assert hass.states.get(mock_entity_id).state == STATE_STANDBY @@ -201,9 +190,7 @@ async def test_state_playing_is_set(hass): "pyps4.Ps4Async.async_get_ps_store_data", ) - with patch(mock_func, return_value=mock_coro(None)), patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func, return_value=None): await mock_ddp_response(hass, MOCK_STATUS_PLAYING) assert hass.states.get(mock_entity_id).state == STATE_PLAYING @@ -213,8 +200,7 @@ async def test_state_idle_is_set(hass): """Test that state is set to idle.""" mock_entity_id = await setup_mock_component(hass) - with patch(MOCK_SAVE, side_effect=MagicMock()): - await mock_ddp_response(hass, MOCK_STATUS_IDLE) + await mock_ddp_response(hass, MOCK_STATUS_IDLE) assert hass.states.get(mock_entity_id).state == STATE_IDLE @@ -240,9 +226,7 @@ async def test_media_attributes_are_fetched(hass): mock_result.cover_art = MOCK_TITLE_ART_URL mock_result.game_type = "game" - with patch(mock_func, return_value=mock_coro(mock_result)) as mock_fetch, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func, return_value=mock_result) as mock_fetch: await mock_ddp_response(hass, MOCK_STATUS_PLAYING) mock_state = hass.states.get(mock_entity_id) @@ -258,19 +242,17 @@ async def test_media_attributes_are_fetched(hass): assert mock_attrs.get(ATTR_MEDIA_CONTENT_TYPE) == MOCK_TITLE_TYPE -async def test_media_attributes_are_loaded(hass): +async def test_media_attributes_are_loaded(hass, patch_load_json): """Test that media attributes are loaded.""" mock_entity_id = await setup_mock_component(hass) - mock_data = {MOCK_TITLE_ID: MOCK_GAMES_DATA_LOCKED} - mock_func = "{}{}".format( - "homeassistant.components.ps4.media_player.", - "pyps4.Ps4Async.async_get_ps_store_data", - ) + patch_load_json.return_value = {MOCK_TITLE_ID: MOCK_GAMES_DATA_LOCKED} - with patch(mock_func, return_value=mock_coro(None)) as mock_fetch, patch( - MOCK_SAVE, side_effect=MagicMock() - ): - await mock_ddp_response(hass, MOCK_STATUS_PLAYING, mock_data) + with patch( + "homeassistant.components.ps4.media_player." + "pyps4.Ps4Async.async_get_ps_store_data", + return_value=None, + ) as mock_fetch: + await mock_ddp_response(hass, MOCK_STATUS_PLAYING) mock_state = hass.states.get(mock_entity_id) mock_attrs = dict(mock_state.attributes) @@ -372,9 +354,7 @@ async def test_turn_on(hass): "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.wakeup" ) - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func) as mock_call: await hass.services.async_call( "media_player", "turn_on", {ATTR_ENTITY_ID: mock_entity_id} ) @@ -390,9 +370,7 @@ async def test_turn_off(hass): "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.standby" ) - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func) as mock_call: await hass.services.async_call( "media_player", "turn_off", {ATTR_ENTITY_ID: mock_entity_id} ) @@ -408,9 +386,7 @@ async def test_media_pause(hass): "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.remote_control" ) - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func) as mock_call: await hass.services.async_call( "media_player", "media_pause", {ATTR_ENTITY_ID: mock_entity_id} ) @@ -426,9 +402,7 @@ async def test_media_stop(hass): "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.remote_control" ) - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch(mock_func) as mock_call: await hass.services.async_call( "media_player", "media_stop", {ATTR_ENTITY_ID: mock_entity_id} ) @@ -437,46 +411,34 @@ async def test_media_stop(hass): assert len(mock_call.mock_calls) == 1 -async def test_select_source(hass): +async def test_select_source(hass, patch_load_json): """Test that select source service calls function with title.""" - mock_data = {MOCK_TITLE_ID: MOCK_GAMES_DATA} - with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE), patch( - MOCK_LOAD, return_value=mock_data - ): + patch_load_json.return_value = {MOCK_TITLE_ID: MOCK_GAMES_DATA} + with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - mock_func = "{}{}".format( - "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.start_title" - ) - - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() + with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( + "homeassistant.components.ps4.media_player.PS4Device.async_update" ): # Test with title name. await hass.services.async_call( "media_player", "select_source", {ATTR_ENTITY_ID: mock_entity_id, ATTR_INPUT_SOURCE: MOCK_TITLE_NAME}, + blocking=True, ) - await hass.async_block_till_done() assert len(mock_call.mock_calls) == 1 -async def test_select_source_caps(hass): +async def test_select_source_caps(hass, patch_load_json): """Test that select source service calls function with upper case title.""" - mock_data = {MOCK_TITLE_ID: MOCK_GAMES_DATA} - with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE), patch( - MOCK_LOAD, return_value=mock_data - ): + patch_load_json.return_value = {MOCK_TITLE_ID: MOCK_GAMES_DATA} + with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - mock_func = "{}{}".format( - "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.start_title" - ) - - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() + with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( + "homeassistant.components.ps4.media_player.PS4Device.async_update" ): # Test with title name in caps. await hass.services.async_call( @@ -486,34 +448,28 @@ async def test_select_source_caps(hass): ATTR_ENTITY_ID: mock_entity_id, ATTR_INPUT_SOURCE: MOCK_TITLE_NAME.upper(), }, + blocking=True, ) - await hass.async_block_till_done() assert len(mock_call.mock_calls) == 1 -async def test_select_source_id(hass): +async def test_select_source_id(hass, patch_load_json): """Test that select source service calls function with Title ID.""" - mock_data = {MOCK_TITLE_ID: MOCK_GAMES_DATA} - with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE), patch( - MOCK_LOAD, return_value=mock_data - ): + patch_load_json.return_value = {MOCK_TITLE_ID: MOCK_GAMES_DATA} + with patch("pyps4_2ndscreen.ps4.get_status", return_value=MOCK_STATUS_IDLE): mock_entity_id = await setup_mock_component(hass) - mock_func = "{}{}".format( - "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.start_title" - ) - - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() + with patch("pyps4_2ndscreen.ps4.Ps4Async.start_title") as mock_call, patch( + "homeassistant.components.ps4.media_player.PS4Device.async_update" ): # Test with title ID. await hass.services.async_call( "media_player", "select_source", {ATTR_ENTITY_ID: mock_entity_id, ATTR_INPUT_SOURCE: MOCK_TITLE_ID}, + blocking=True, ) - await hass.async_block_till_done() assert len(mock_call.mock_calls) == 1 @@ -521,17 +477,14 @@ async def test_select_source_id(hass): async def test_ps4_send_command(hass): """Test that ps4 send command service calls function.""" mock_entity_id = await setup_mock_component(hass) - mock_func = "{}{}".format( - "homeassistant.components.ps4.media_player.", "pyps4.Ps4Async.remote_control" - ) - with patch(mock_func, return_value=MagicMock()) as mock_call, patch( - MOCK_SAVE, side_effect=MagicMock() - ): + with patch("pyps4_2ndscreen.ps4.Ps4Async.remote_control") as mock_call: await hass.services.async_call( - DOMAIN, "send_command", {ATTR_ENTITY_ID: mock_entity_id, ATTR_COMMAND: "ps"} + DOMAIN, + "send_command", + {ATTR_ENTITY_ID: mock_entity_id, ATTR_COMMAND: "ps"}, + blocking=True, ) - await hass.async_block_till_done() assert len(mock_call.mock_calls) == 1 diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 6efcd4e463e..27824623d23 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1,6 +1,5 @@ """Test Zeroconf component setup process.""" -from unittest.mock import patch - +from asynctest import patch import pytest from zeroconf import ServiceInfo, ServiceStateChange @@ -75,7 +74,7 @@ async def test_setup(hass, mock_zeroconf): expected_flow_calls = 0 for matching_components in zc_gen.ZEROCONF.values(): expected_flow_calls += len(matching_components) - assert len(mock_config_flow.mock_calls) == expected_flow_calls * 2 + assert len(mock_config_flow.mock_calls) == expected_flow_calls async def test_homekit_match_partial_space(hass, mock_zeroconf): @@ -91,7 +90,7 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == 1 - assert len(mock_config_flow.mock_calls) == 2 + assert len(mock_config_flow.mock_calls) == 1 assert mock_config_flow.mock_calls[0][1][0] == "lifx" @@ -110,7 +109,7 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == 1 - assert len(mock_config_flow.mock_calls) == 2 + assert len(mock_config_flow.mock_calls) == 1 assert mock_config_flow.mock_calls[0][1][0] == "rachio" @@ -127,7 +126,7 @@ async def test_homekit_match_full(hass, mock_zeroconf): assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert len(mock_service_browser.mock_calls) == 1 - assert len(mock_config_flow.mock_calls) == 2 + assert len(mock_config_flow.mock_calls) == 1 assert mock_config_flow.mock_calls[0][1][0] == "hue" From 5f775c14278074a75464b538e69a66813a31e549 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 25 Apr 2020 00:08:30 +0000 Subject: [PATCH 052/511] [ci skip] Translation update --- .../components/airvisual/translations/lb.json | 26 +++++++++++++++++-- .../components/airvisual/translations/vi.json | 11 ++++++++ .../components/atag/translations/lb.json | 20 ++++++++++++++ .../components/atag/translations/nl.json | 20 ++++++++++++++ .../components/braviatv/translations/lb.json | 3 +++ .../coronavirus/translations/nl.json | 15 +++++++++++ .../components/deconz/translations/lb.json | 2 ++ .../components/deconz/translations/nl.json | 12 +++++++-- .../components/flume/translations/lb.json | 3 ++- .../components/fritzbox/translations/nl.json | 15 +++++++++-- .../geonetnz_quakes/translations/nl.json | 3 +++ .../components/harmony/translations/nl.json | 24 +++++++++++++++++ .../components/hue/translations/lb.json | 6 ++++- .../islamic_prayer_times/translations/nl.json | 23 ++++++++++++++++ .../components/local_ip/translations/lb.json | 3 +++ .../components/nuheat/translations/pl.json | 24 +++++++++++++++++ .../components/nut/translations/lb.json | 6 +++-- .../components/nws/translations/lb.json | 4 ++- .../panasonic_viera/translations/lb.json | 2 ++ .../panasonic_viera/translations/nl.json | 16 +++++++++--- .../pvpc_hourly_pricing/translations/pl.json | 17 ++++++++++++ .../components/roomba/translations/lb.json | 7 ++++- .../smartthings/translations/lb.json | 4 +++ .../synology_dsm/translations/lb.json | 8 ++++-- .../components/tado/translations/lb.json | 14 +++++++++- .../components/tesla/translations/pl.json | 1 + .../components/timer/translations/nl.json | 6 ++--- .../totalconnect/translations/lb.json | 3 ++- .../components/unifi/translations/lb.json | 1 + .../components/vacuum/translations/nl.json | 2 +- .../components/wwlln/translations/nl.json | 3 +++ .../components/zha/translations/nl.json | 8 ++++++ 32 files changed, 289 insertions(+), 23 deletions(-) create mode 100644 homeassistant/components/airvisual/translations/vi.json create mode 100644 homeassistant/components/atag/translations/lb.json create mode 100644 homeassistant/components/atag/translations/nl.json create mode 100644 homeassistant/components/coronavirus/translations/nl.json create mode 100644 homeassistant/components/harmony/translations/nl.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/nl.json create mode 100644 homeassistant/components/nuheat/translations/pl.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/pl.json diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index 69e8ea6d380..d5f78eccf0b 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -4,15 +4,37 @@ "already_configured": "D\u00ebs Koordinate si schon registr\u00e9iert." }, "error": { - "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel" + "general_error": "Onbekannten Feeler", + "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel", + "unable_to_connect": "Kann sech net mat der Node/Pri verbannen." }, "step": { - "user": { + "geography": { "data": { "api_key": "API Schl\u00ebssel", "latitude": "Breedegrad", "longitude": "L\u00e4ngegrad" }, + "description": "Benotz Airvisual cloud API fir eng geografescher Lag z'iwwerwaachen.", + "title": "Geografie ariichten" + }, + "node_pro": { + "data": { + "ip_address": "IP Adresse / Numm vun der Unit\u00e9it", + "password": "Passwuert vun der Unit\u00e9it" + }, + "description": "Pers\u00e9inlech Airvisual Unit\u00e9it iwwerwaachen. Passwuert kann vum UI vum Apparat ausgelies ginn.", + "title": "Airvisual Node/Pro ariichten" + }, + "user": { + "data": { + "api_key": "API Schl\u00ebssel", + "cloud_api": "Geografesche Standuert", + "latitude": "Breedegrad", + "longitude": "L\u00e4ngegrad", + "node_pro": "Airvisual Node Pro", + "type": "Typ vun der Integratioun" + }, "description": "Loft Qualit\u00e9it an enger geografescher Lag iwwerwaachen.", "title": "AirVisual konfigur\u00e9ieren" } diff --git a/homeassistant/components/airvisual/translations/vi.json b/homeassistant/components/airvisual/translations/vi.json new file mode 100644 index 00000000000..6246d8997da --- /dev/null +++ b/homeassistant/components/airvisual/translations/vi.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "type": "Lo\u1ea1i t\u00edch h\u1ee3p" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/lb.json b/homeassistant/components/atag/translations/lb.json new file mode 100644 index 00000000000..dcb32f3eedc --- /dev/null +++ b/homeassistant/components/atag/translations/lb.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "N\u00ebmmen 1 Atag Apparat kann am Home Assistant dob\u00e4igesat ginn" + }, + "error": { + "connection_error": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol." + }, + "step": { + "user": { + "data": { + "host": "Apparat", + "port": "Port (10000)" + }, + "title": "Mam Apparat verbannen" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/nl.json b/homeassistant/components/atag/translations/nl.json new file mode 100644 index 00000000000..14da45b8eb9 --- /dev/null +++ b/homeassistant/components/atag/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Er kan slechts \u00e9\u00e9n Atag-apparaat worden toegevoegd aan Home Assistant " + }, + "error": { + "connection_error": "Verbinding mislukt, probeer het opnieuw" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Poort (10000)" + }, + "title": "Verbinding maken met het apparaat" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/lb.json b/homeassistant/components/braviatv/translations/lb.json index 9a64abadb63..37366eee6dd 100644 --- a/homeassistant/components/braviatv/translations/lb.json +++ b/homeassistant/components/braviatv/translations/lb.json @@ -4,6 +4,7 @@ "already_configured": "D\u00ebse Fernseh ass scho konfigur\u00e9iert." }, "error": { + "cannot_connect": "Feeler beim verbannen, ong\u00ebltege Numm oder PIN code.", "invalid_host": "Ong\u00ebltege Numm oder IP Adresse.", "unsupported_model": "D\u00e4in TV Modell g\u00ebtt net \u00ebnnerst\u00ebtzt." }, @@ -12,12 +13,14 @@ "data": { "pin": "PIN Code" }, + "description": "G\u00ebff de PIN code an deen op der Sony Bravia TV ugewise g\u00ebtt.\n\nFalls kee PIN code ugewise g\u00ebtt muss den Home Assistant um Fernseh ofgemellt ginn, um TV: Settings -> Network -> Remote device settings -> Unregister remote device.", "title": "Sony Bravia TV erlaaben" }, "user": { "data": { "host": "TV Host Numm oder IP Adresse" }, + "description": "Sony Bravia TV Integratioun ariichten. Falls et Problemer mat der Konfiguratioun g\u00ebtt g\u00e9i op:\nhttps://www.home-assistant.io/integrations/braviatv\nStell s\u00e9cher dass d\u00e4in Fernseh un ass.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/coronavirus/translations/nl.json b/homeassistant/components/coronavirus/translations/nl.json new file mode 100644 index 00000000000..d306894f7d0 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/nl.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Dit land is al geconfigureerd." + }, + "step": { + "user": { + "data": { + "country": "Land" + }, + "title": "Kies een land om te monitoren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index ab96460dc79..c6f2dfbf189 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -48,6 +48,7 @@ "device_automation": { "trigger_subtype": { "both_buttons": "B\u00e9id Kn\u00e4ppchen", + "bottom_buttons": "\u00cbnnescht Kn\u00e4ppchen", "button_1": "\u00c9ischte Kn\u00e4ppchen", "button_2": "Zweete Kn\u00e4ppchen", "button_3": "Dr\u00ebtte Kn\u00e4ppchen", @@ -64,6 +65,7 @@ "side_4": "S\u00e4it 4", "side_5": "S\u00e4it 5", "side_6": "S\u00e4it 6", + "top_buttons": "Iewescht Kn\u00e4ppchen", "turn_off": "Ausschalten", "turn_on": "Uschalten" }, diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index e1a9583e03b..d9d64070c88 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -34,7 +34,14 @@ "data": { "host": "Host", "port": "Poort" - } + }, + "title": "Configureer deCONZ gateway" + }, + "user": { + "data": { + "host": "Selecteer gevonden deCONZ gateway" + }, + "title": "Selecteer DeCONZ gateway" } } }, @@ -97,7 +104,8 @@ "allow_clip_sensor": "DeCONZ CLIP sensoren toestaan", "allow_deconz_groups": "Sta deCONZ-lichtgroepen toe" }, - "description": "Configureer de zichtbaarheid van deCONZ-apparaattypen" + "description": "Configureer de zichtbaarheid van deCONZ-apparaattypen", + "title": "deCONZ opties" } } } diff --git a/homeassistant/components/flume/translations/lb.json b/homeassistant/components/flume/translations/lb.json index 33afc930e8e..aad8a496a11 100644 --- a/homeassistant/components/flume/translations/lb.json +++ b/homeassistant/components/flume/translations/lb.json @@ -15,7 +15,8 @@ "client_secret": "Client Schl\u00ebssel", "password": "Passwuert", "username": "Benotzernumm" - } + }, + "title": "Verbann dech mat dengem Flume Kont." } } } diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 4617a07a5b6..3d8c94ab5b6 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -1,20 +1,31 @@ { "config": { + "abort": { + "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", + "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", + "not_found": "Geen ondersteunde AVM FRITZ!Box gevonden op het netwerk." + }, "error": { "auth_failed": "Ongeldige gebruikersnaam of wachtwoord" }, + "flow_title": "AVM FRITZ!Box: {name}", "step": { "confirm": { "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Wilt u {name} instellen?", + "title": "AVM FRITZ!Box" }, "user": { "data": { + "host": "Host- of IP-adres", "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "description": "Voer uw AVM FRITZ!Box informatie in.", + "title": "AVM FRITZ!Box" } } } diff --git a/homeassistant/components/geonetnz_quakes/translations/nl.json b/homeassistant/components/geonetnz_quakes/translations/nl.json index 4fb50788fc9..865860a5adf 100644 --- a/homeassistant/components/geonetnz_quakes/translations/nl.json +++ b/homeassistant/components/geonetnz_quakes/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Locatie is al geconfigureerd." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/harmony/translations/nl.json b/homeassistant/components/harmony/translations/nl.json new file mode 100644 index 00000000000..a896cab0877 --- /dev/null +++ b/homeassistant/components/harmony/translations/nl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "Wil je {name} ({host}) instellen?", + "title": "Logitech Harmony Hub instellen" + }, + "user": { + "data": { + "host": "Hostnaam of IP-adres", + "name": "Naam van hub" + }, + "title": "Logitech Harmony Hub instellen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/lb.json b/homeassistant/components/hue/translations/lb.json index cbdb31e3b36..02a4924d59f 100644 --- a/homeassistant/components/hue/translations/lb.json +++ b/homeassistant/components/hue/translations/lb.json @@ -35,13 +35,17 @@ "button_4": "V\u00e9ierte Kn\u00e4ppchen", "dim_down": "Verd\u00e4ischteren", "dim_up": "Erhellen", + "double_buttons_1_3": "\u00c9ischte an Dr\u00ebtte Kn\u00e4ppchen", + "double_buttons_2_4": "Zweete a V\u00e9ierte Kn\u00e4ppchen", "turn_off": "Ausschalten", "turn_on": "Uschalten" }, "trigger_type": { "remote_button_long_release": "\"{subtype}\" Kn\u00e4ppche no laangem unhalen lassgelooss", "remote_button_short_press": "\"{subtype}\" Kn\u00e4ppche gedr\u00e9ckt", - "remote_button_short_release": "\"{subtype}\" Kn\u00e4ppche lassgelooss" + "remote_button_short_release": "\"{subtype}\" Kn\u00e4ppche lassgelooss", + "remote_double_button_long_press": "B\u00e9id \"{subtype}\" no laangem unhalen lassgelooss", + "remote_double_button_short_press": "B\u00e9id \"{subtype}\" lassgeloss" } } } \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/nl.json b/homeassistant/components/islamic_prayer_times/translations/nl.json new file mode 100644 index 00000000000..b0cab2d938d --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Slechts \u00e9\u00e9n exemplaar is nodig." + }, + "step": { + "user": { + "description": "Wil je Islamic Prayer Times opzetten?", + "title": "Stel Islamitische gebedstijden in" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calc_method": "Berekeningsmethode voor het gebed" + } + } + } + }, + "title": "Islamitische gebedstijden" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/lb.json b/homeassistant/components/local_ip/translations/lb.json index a404e1747e9..8d141a5f35f 100644 --- a/homeassistant/components/local_ip/translations/lb.json +++ b/homeassistant/components/local_ip/translations/lb.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun Local IP ass erlaabt." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/nuheat/translations/pl.json b/homeassistant/components/nuheat/translations/pl.json new file mode 100644 index 00000000000..bff4192f018 --- /dev/null +++ b/homeassistant/components/nuheat/translations/pl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Termostat jest ju\u017c skonfigurowany." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "invalid_thermostat": "Numer seryjny termostatu jest nieprawid\u0142owy.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "serial_number": "Numer seryjny termostatu", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Musisz uzyska\u0107 numeryczny numer seryjny lub identyfikator termostatu, loguj\u0105c si\u0119 na https://MyNuHeat.com i wybieraj\u0105c termostat(y).", + "title": "Po\u0142\u0105cz z NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/lb.json b/homeassistant/components/nut/translations/lb.json index 49f47d3fa3e..fa7dc4da26d 100644 --- a/homeassistant/components/nut/translations/lb.json +++ b/homeassistant/components/nut/translations/lb.json @@ -18,7 +18,8 @@ "data": { "alias": "Alias", "resources": "Ressourcen" - } + }, + "title": "UPS fir z'iwwerwaachen auswielen" }, "user": { "data": { @@ -35,7 +36,8 @@ "step": { "init": { "data": { - "resources": "Ressourcen" + "resources": "Ressourcen", + "scan_interval": "Scan Intervall (sekonnen)" }, "description": "Sensor Ressourcen auswielen" } diff --git a/homeassistant/components/nws/translations/lb.json b/homeassistant/components/nws/translations/lb.json index 6a3e33222f3..054e3bf9642 100644 --- a/homeassistant/components/nws/translations/lb.json +++ b/homeassistant/components/nws/translations/lb.json @@ -14,7 +14,9 @@ "latitude": "Breedegrad", "longitude": "L\u00e4ngegrad", "station": "METAR Statioun's Code" - } + }, + "description": "Falls kee METAR Statioun's Code uginn ass, ginn L\u00e4ngegrad a Breedegrad benotzt fir d'Statioun auszewielen.", + "title": "Mam Nationale Wieder D\u00e9ngscht verbannen" } } } diff --git a/homeassistant/components/panasonic_viera/translations/lb.json b/homeassistant/components/panasonic_viera/translations/lb.json index b35af6e1ba8..0fb2a8c703f 100644 --- a/homeassistant/components/panasonic_viera/translations/lb.json +++ b/homeassistant/components/panasonic_viera/translations/lb.json @@ -1,6 +1,8 @@ { "config": { "abort": { + "already_configured": "D\u00ebs Panasonic Viera TA ass scho konfigur\u00e9iert.", + "not_connected": "Verbindung mam Panasonic Viera TV as \u00ebnnerbrach. Kuck Log fir w\u00e9ider Informatiounen.", "unknown": "Onbekannte Feeler opgetrueden. Kuck d'Logs fir m\u00e9i Informatiounen." }, "error": { diff --git a/homeassistant/components/panasonic_viera/translations/nl.json b/homeassistant/components/panasonic_viera/translations/nl.json index 79bc899c477..1f38ff9c441 100644 --- a/homeassistant/components/panasonic_viera/translations/nl.json +++ b/homeassistant/components/panasonic_viera/translations/nl.json @@ -1,21 +1,31 @@ { "config": { + "abort": { + "already_configured": "Deze Panasonic Viera TV is al geconfigureerd.", + "not_connected": "De externe verbinding met uw Panasonic Viera-tv was verbroken. Controleer de logs voor meer informatie.", + "unknown": "Er is een onbekende fout opgetreden. Controleer de logs voor meer informatie." + }, "error": { - "invalid_pin_code": "De ingevoerde pincode is ongeldig" + "invalid_pin_code": "De ingevoerde pincode is ongeldig", + "not_connected": "Kon geen externe verbinding met uw Panasonic Viera TV tot stand brengen" }, "step": { "pairing": { "data": { "pin": "PIN" - } + }, + "description": "Voer de PIN-code in die op uw TV wordt weergegeven", + "title": "Koppelen" }, "user": { "data": { "host": "IP-adres", "name": "Naam" }, + "description": "Voer het IP-adres van uw Panasonic Viera TV in", "title": "Uw tv instellen" } } - } + }, + "title": "Panasonic Viera" } \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pl.json b/homeassistant/components/pvpc_hourly_pricing/translations/pl.json new file mode 100644 index 00000000000..ae5f27fb7c2 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Integracja jest ju\u017c skonfigurowana z istniej\u0105cym sensorem dla tej taryfy." + }, + "step": { + "user": { + "data": { + "name": "Nazwa sensora", + "tariff": "Zakontraktowana taryfa (1, 2 lub 3 okresy)" + }, + "description": "Ten czujnik u\u017cywa oficjalnego interfejsu API w celu uzyskania [godzinowej ceny energii elektrycznej (PVPC)] (https://www.esios.ree.es/es/pvpc) w Hiszpanii. \n Aby uzyska\u0107 bardziej szczeg\u00f3\u0142owe wyja\u015bnienia, odwied\u017a [dokumentacj\u0119 dotycz\u0105c\u0105 integracji] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n Wybierz stawk\u0119 umown\u0105 na podstawie liczby okres\u00f3w rozliczeniowych dziennie: \n - 1 okres: normalny \n - 2 okresy: dyskryminacja (nocna stawka) \n - 3 okresy: samoch\u00f3d elektryczny (stawka nocna za 3 okresy)", + "title": "Wyb\u00f3r taryfy" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 08933fc16f9..70671503e51 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", "unknown": "Onerwaarte Feeler" }, "step": { @@ -8,9 +9,12 @@ "data": { "blid": "BLID", "certificate": "Zertifikat", + "continuous": "Kontinu\u00e9ierlech", + "delay": "Delai", "host": "Host Numm oder IP Adresse", "password": "Passwuert" - } + }, + "title": "Mam Apparat verbannen" } } }, @@ -18,6 +22,7 @@ "step": { "init": { "data": { + "continuous": "Kontinu\u00e9ierlech", "delay": "Delai" } } diff --git a/homeassistant/components/smartthings/translations/lb.json b/homeassistant/components/smartthings/translations/lb.json index 9c601917055..623e00870b8 100644 --- a/homeassistant/components/smartthings/translations/lb.json +++ b/homeassistant/components/smartthings/translations/lb.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_available_locations": "Keng disponibel Smartthings Standuerte fir am Home Assistant anzeriichten." + }, "error": { "app_setup_error": "Kann SmartApp net install\u00e9ieren. Prob\u00e9iert w.e.g. nach emol.", "token_forbidden": "De Jeton huet net d\u00e9i n\u00e9ideg OAuth M\u00e9iglechkeeten.", @@ -15,6 +18,7 @@ "data": { "access_token": "Acc\u00e8ss Jeton" }, + "description": "G\u00ebff w.e.g. ee pers\u00e9inlechen SmartThings [Acc\u00e8s Jeton]({token_url}) un dee via [d'Instruktiounen] ({component_url}) erstallt gouf. D\u00ebse g\u00ebtte benotzt fir d'Integratioun vum SmartThings Kont am Home Assistant.", "title": "Pers\u00e9inlechen Acc\u00e8ss Jeton uginn" }, "select_location": { diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 3c5f2c2a6b9..5453b078a3e 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -4,15 +4,19 @@ "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { + "connection": "Feeler beim verbannen Iwwerpr\u00e9if w.e.g. den Numm, Passwuert & SSL", "login": "Feeler beim Login: iwwerpr\u00e9if de Benotzernumm & Passwuert", - "otp_failed": "Feeler mam 2-Faktor-Authentifikatiouns, prob\u00e9ier mat engem neie Code" + "missing_data": "Donn\u00e9\u00ebe feelen, prob\u00e9ier sp\u00e9ider oder mat enger aner Konfiguratioun", + "otp_failed": "Feeler mam 2-Faktor-Authentifikatiouns, prob\u00e9ier mat engem neie Code", + "unknown": "Onbekannte Feeler opgetrueden. Kuck d'Logs fir m\u00e9i Informatiounen." }, "flow_title": "Synology DSM {name} ({host})", "step": { "2sa": { "data": { "otp_code": "Code" - } + }, + "title": "Synology DSM: 2-Faktor-Authentifikatioun" }, "link": { "data": { diff --git a/homeassistant/components/tado/translations/lb.json b/homeassistant/components/tado/translations/lb.json index ba10353948c..e84720a2de1 100644 --- a/homeassistant/components/tado/translations/lb.json +++ b/homeassistant/components/tado/translations/lb.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "no_homes": "Keng Immobilien mat d\u00ebsem Tado Kont verbonnen.", "unknown": "Onerwaarte Feeler" }, "step": { @@ -13,7 +14,18 @@ "data": { "password": "Passwuert", "username": "Benotzernumm" - } + }, + "title": "Verbann dech mat dengem Tado Kont." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Fallback Modus aktiv\u00e9ieren" + }, + "title": "Tado Optiounen \u00e4nneren" } } } diff --git a/homeassistant/components/tesla/translations/pl.json b/homeassistant/components/tesla/translations/pl.json index b961d12e5a8..e2a6b6a09c8 100644 --- a/homeassistant/components/tesla/translations/pl.json +++ b/homeassistant/components/tesla/translations/pl.json @@ -21,6 +21,7 @@ "step": { "init": { "data": { + "enable_wake_on_start": "Wymu\u015b wybudzenie samochod\u00f3w podczas uruchamiania", "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji" } } diff --git a/homeassistant/components/timer/translations/nl.json b/homeassistant/components/timer/translations/nl.json index c7811e2cd02..1cfd9c72c46 100644 --- a/homeassistant/components/timer/translations/nl.json +++ b/homeassistant/components/timer/translations/nl.json @@ -1,9 +1,9 @@ { "state": { "_": { - "active": "actief", - "idle": "inactief", - "paused": "gepauzeerd" + "active": "Actief", + "idle": "Inactief", + "paused": "Gepauzeerd" } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/lb.json b/homeassistant/components/totalconnect/translations/lb.json index 23825000fdb..a75e63e9b58 100644 --- a/homeassistant/components/totalconnect/translations/lb.json +++ b/homeassistant/components/totalconnect/translations/lb.json @@ -11,7 +11,8 @@ "data": { "password": "Passwuert", "username": "Benotzernumm" - } + }, + "title": "Total Connect" } } } diff --git a/homeassistant/components/unifi/translations/lb.json b/homeassistant/components/unifi/translations/lb.json index f6e9a6c2c4b..901cde0dab4 100644 --- a/homeassistant/components/unifi/translations/lb.json +++ b/homeassistant/components/unifi/translations/lb.json @@ -37,6 +37,7 @@ "device_tracker": { "data": { "detection_time": "Z\u00e4it a Sekonne vum leschten Z\u00e4itpunkt un bis den Apparat als \u00ebnnerwee consider\u00e9iert g\u00ebtt", + "ignore_wired_bug": "UniFi wired bug log ausschalten", "ssid_filter": "SSIDs auswielen fir Clienten ze verfollegen", "track_clients": "Netzwierk Cliente verfollegen", "track_devices": "Netzwierk Apparater (Ubiquiti Apparater) verfollegen", diff --git a/homeassistant/components/vacuum/translations/nl.json b/homeassistant/components/vacuum/translations/nl.json index f773ec18509..3fbb0ae50be 100644 --- a/homeassistant/components/vacuum/translations/nl.json +++ b/homeassistant/components/vacuum/translations/nl.json @@ -21,7 +21,7 @@ "idle": "Inactief", "off": "Uit", "on": "Aan", - "paused": "Gepauseerd", + "paused": "Gepauzeerd", "returning": "Terugkeren naar dock" } }, diff --git a/homeassistant/components/wwlln/translations/nl.json b/homeassistant/components/wwlln/translations/nl.json index 442420b1c5d..34388295976 100644 --- a/homeassistant/components/wwlln/translations/nl.json +++ b/homeassistant/components/wwlln/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Deze locatie is al geregistreerd." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index c82271a9ffb..f3eb4009f02 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -53,6 +53,14 @@ "device_shaken": "Apparaat geschud", "device_slid": "Apparaat geschoven \"{subtype}\"\".", "device_tilted": "Apparaat gekanteld", + "remote_button_alt_double_press": "\" {subtype} \" knop tweemaal aangeklikt (alternate mode)", + "remote_button_alt_long_press": "\" {subtype} \" knop continu ingedrukt (alternate mode)", + "remote_button_alt_long_release": "\"{subtype}\" knop losgelaten (alternate mode)", + "remote_button_alt_quadruple_press": "\" {subtype} \" knop viervoudig aangeklikt (alternate mode)", + "remote_button_alt_quintuple_press": "\" {subtype} \" knop vijfmaal geklikt (alternate mode)", + "remote_button_alt_short_press": "\" {subtype} \" knop ingedrukt (alternate mode)", + "remote_button_alt_short_release": "\"{subtype}\" knop losgelaten (alternate mode)", + "remote_button_alt_triple_press": "\" {subtype} \" knop driemaal geklikt (alternate mode)", "remote_button_double_press": "\"{subtype}\" knop dubbel geklikt", "remote_button_long_press": "\" {subtype} \" knop continu ingedrukt", "remote_button_long_release": "\"{subtype}\" knop losgelaten na lang indrukken van de knop", From 56f7c3b55cd7a9b36256a5efe5d37b29054c9d4c Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Sat, 25 Apr 2020 15:14:19 +0200 Subject: [PATCH 053/511] Add Signalmessenger group recipients (#34419) --- homeassistant/components/signal_messenger/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/signal_messenger/manifest.json b/homeassistant/components/signal_messenger/manifest.json index f1db6a8af30..dcbf41307c4 100644 --- a/homeassistant/components/signal_messenger/manifest.json +++ b/homeassistant/components/signal_messenger/manifest.json @@ -3,5 +3,5 @@ "name": "Signal Messenger", "documentation": "https://www.home-assistant.io/integrations/signal_messenger", "codeowners": ["@bbernhard"], - "requirements": ["pysignalclirestapi==0.2.4"] + "requirements": ["pysignalclirestapi==0.3.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index cc6f15be0db..df8e3f15252 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1551,7 +1551,7 @@ pysesame2==1.0.1 pysher==1.0.1 # homeassistant.components.signal_messenger -pysignalclirestapi==0.2.4 +pysignalclirestapi==0.3.4 # homeassistant.components.sma pysma==0.3.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 620adff681a..307b0de5b1e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -614,7 +614,7 @@ pyps4-2ndscreen==1.0.7 pyqwikswitch==0.93 # homeassistant.components.signal_messenger -pysignalclirestapi==0.2.4 +pysignalclirestapi==0.3.4 # homeassistant.components.sma pysma==0.3.5 From 62bc02fddaecd07a716bef6b0ecd2d3295781fb9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 25 Apr 2020 18:00:57 +0200 Subject: [PATCH 054/511] Rename MediaPlayerDevice to MediaPlayerEntity (#34592) --- .../components/androidtv/media_player.py | 4 ++-- .../components/anthemav/media_player.py | 4 ++-- .../components/apple_tv/media_player.py | 4 ++-- .../components/aquostv/media_player.py | 4 ++-- .../components/arcam_fmj/media_player.py | 4 ++-- .../components/blackbird/media_player.py | 4 ++-- .../components/bluesound/media_player.py | 4 ++-- .../components/braviatv/media_player.py | 4 ++-- homeassistant/components/cast/media_player.py | 4 ++-- .../components/channels/media_player.py | 4 ++-- .../components/clementine/media_player.py | 4 ++-- homeassistant/components/cmus/media_player.py | 4 ++-- homeassistant/components/demo/media_player.py | 4 ++-- .../components/denon/media_player.py | 4 ++-- .../components/denonavr/media_player.py | 4 ++-- .../components/directv/media_player.py | 6 +++--- .../components/dlna_dmr/media_player.py | 4 ++-- .../components/dunehd/media_player.py | 4 ++-- homeassistant/components/emby/media_player.py | 4 ++-- .../components/enigma2/media_player.py | 4 ++-- .../components/epson/media_player.py | 4 ++-- .../frontier_silicon/media_player.py | 4 ++-- .../components/gpmdp/media_player.py | 4 ++-- .../components/gstreamer/media_player.py | 4 ++-- .../harman_kardon_avr/media_player.py | 4 ++-- .../components/hdmi_cec/media_player.py | 4 ++-- homeassistant/components/heos/media_player.py | 4 ++-- .../homekit_controller/media_player.py | 4 ++-- .../components/horizon/media_player.py | 4 ++-- .../components/itunes/media_player.py | 6 +++--- homeassistant/components/kef/media_player.py | 4 ++-- homeassistant/components/kodi/__init__.py | 2 +- homeassistant/components/kodi/media_player.py | 4 ++-- .../components/lg_netcast/media_player.py | 4 ++-- .../components/lg_soundbar/media_player.py | 4 ++-- .../components/media_player/__init__.py | 16 ++++++++++++++-- .../components/mediaroom/media_player.py | 4 ++-- .../components/monoprice/media_player.py | 4 ++-- .../components/mpchc/media_player.py | 4 ++-- homeassistant/components/mpd/media_player.py | 4 ++-- homeassistant/components/nad/media_player.py | 6 +++--- .../components/onkyo/media_player.py | 4 ++-- .../components/openhome/media_player.py | 4 ++-- .../panasonic_bluray/media_player.py | 4 ++-- .../panasonic_viera/media_player.py | 4 ++-- .../components/pandora/media_player.py | 5 ++--- .../components/philips_js/media_player.py | 4 ++-- .../components/pjlink/media_player.py | 4 ++-- homeassistant/components/plex/media_player.py | 4 ++-- homeassistant/components/ps4/media_player.py | 4 ++-- homeassistant/components/roku/media_player.py | 4 ++-- .../components/russound_rio/media_player.py | 4 ++-- .../components/russound_rnet/media_player.py | 4 ++-- .../components/samsungtv/media_player.py | 4 ++-- .../components/sisyphus/media_player.py | 4 ++-- .../components/snapcast/media_player.py | 6 +++--- .../components/songpal/media_player.py | 4 ++-- .../components/sonos/media_player.py | 4 ++-- .../components/soundtouch/media_player.py | 4 ++-- .../components/spotify/media_player.py | 4 ++-- .../components/squeezebox/media_player.py | 6 +++--- .../components/ue_smart_radio/media_player.py | 4 ++-- .../components/universal/media_player.py | 4 ++-- .../components/vizio/media_player.py | 4 ++-- homeassistant/components/vlc/media_player.py | 4 ++-- .../components/vlc_telnet/media_player.py | 4 ++-- .../components/volumio/media_player.py | 4 ++-- .../components/webostv/media_player.py | 4 ++-- .../components/xiaomi_tv/media_player.py | 4 ++-- .../components/yamaha/media_player.py | 4 ++-- .../yamaha_musiccast/media_player.py | 4 ++-- .../ziggo_mediabox_xl/media_player.py | 4 ++-- .../media_player/test_async_helpers.py | 4 ++-- tests/components/media_player/test_init.py | 19 +++++++++++++++---- .../components/universal/test_media_player.py | 2 +- 75 files changed, 178 insertions(+), 156 deletions(-) diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index a9d7f0ad5be..44085273940 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -16,7 +16,7 @@ from androidtv.constants import APPS, KEYS from androidtv.exceptions import LockNotAcquiredException import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -373,7 +373,7 @@ def adb_decorator(override_available=False): return _adb_decorator -class ADBDevice(MediaPlayerDevice): +class ADBDevice(MediaPlayerEntity): """Representation of an Android TV or Fire TV device.""" def __init__( diff --git a/homeassistant/components/anthemav/media_player.py b/homeassistant/components/anthemav/media_player.py index 434692ce6f5..788fa8db7eb 100644 --- a/homeassistant/components/anthemav/media_player.py +++ b/homeassistant/components/anthemav/media_player.py @@ -4,7 +4,7 @@ import logging import anthemav import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -79,7 +79,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([device]) -class AnthemAVR(MediaPlayerDevice): +class AnthemAVR(MediaPlayerEntity): """Entity reading values from Anthem AVR protocol.""" def __init__(self, avr, name): diff --git a/homeassistant/components/apple_tv/media_player.py b/homeassistant/components/apple_tv/media_player.py index 8b9f3355930..72e7d88b364 100644 --- a/homeassistant/components/apple_tv/media_player.py +++ b/homeassistant/components/apple_tv/media_player.py @@ -3,7 +3,7 @@ import logging import pyatv.const as atv_const -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, @@ -76,7 +76,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([entity]) -class AppleTvDevice(MediaPlayerDevice): +class AppleTvDevice(MediaPlayerEntity): """Representation of an Apple TV device.""" def __init__(self, atv, name, power): diff --git a/homeassistant/components/aquostv/media_player.py b/homeassistant/components/aquostv/media_player.py index d5383590868..35c7e2ae646 100644 --- a/homeassistant/components/aquostv/media_player.py +++ b/homeassistant/components/aquostv/media_player.py @@ -4,7 +4,7 @@ import logging import sharp_aquos_rc import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -121,7 +121,7 @@ def _retry(func): return wrapper -class SharpAquosTVDevice(MediaPlayerDevice): +class SharpAquosTVDevice(MediaPlayerEntity): """Representation of a Aquos TV.""" def __init__(self, name, remote, power_on_enabled=False): diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 92e07a0547e..c9e0c8b8e37 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -6,7 +6,7 @@ from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceC from arcam.fmj.state import State from homeassistant import config_entries -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOUND_MODE, @@ -63,7 +63,7 @@ async def async_setup_entry( return True -class ArcamFmj(MediaPlayerDevice): +class ArcamFmj(MediaPlayerEntity): """Representation of a media device.""" def __init__(self, state: State, name: str, turn_on: Optional[ConfigType]): diff --git a/homeassistant/components/blackbird/media_player.py b/homeassistant/components/blackbird/media_player.py index a0ea369bb9b..9ae696a5276 100644 --- a/homeassistant/components/blackbird/media_player.py +++ b/homeassistant/components/blackbird/media_player.py @@ -6,7 +6,7 @@ from pyblackbird import get_blackbird from serial import SerialException import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -128,7 +128,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class BlackbirdZone(MediaPlayerDevice): +class BlackbirdZone(MediaPlayerEntity): """Representation of a Blackbird matrix zone.""" def __init__(self, blackbird, sources, zone_id, zone_name): diff --git a/homeassistant/components/bluesound/media_player.py b/homeassistant/components/bluesound/media_player.py index eb1ce5f30dc..c0088cb4a2a 100644 --- a/homeassistant/components/bluesound/media_player.py +++ b/homeassistant/components/bluesound/media_player.py @@ -12,7 +12,7 @@ import async_timeout import voluptuous as vol import xmltodict -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, @@ -200,7 +200,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class BluesoundPlayer(MediaPlayerDevice): +class BluesoundPlayer(MediaPlayerEntity): """Representation of a Bluesound Player.""" def __init__(self, hass, host, port=None, name=None, init_callback=None): diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index 718f99d8357..49ae466f23c 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.media_player import ( DEVICE_CLASS_TV, PLATFORM_SCHEMA, - MediaPlayerDevice, + MediaPlayerEntity, ) from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, @@ -116,7 +116,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BraviaTVDevice(MediaPlayerDevice): +class BraviaTVDevice(MediaPlayerEntity): """Representation of a Bravia TV.""" def __init__(self, client, name, pin, unique_id, device_info, ignored_sources): diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index c1729850189..9b6e78f156a 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -12,7 +12,7 @@ from pychromecast.socket_client import ( ) import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -171,7 +171,7 @@ async def _async_setup_platform( hass.async_add_executor_job(setup_internal_discovery, hass) -class CastDevice(MediaPlayerDevice): +class CastDevice(MediaPlayerEntity): """Representation of a Cast device on the network. This class is the holder of the pychromecast.Chromecast object and its diff --git a/homeassistant/components/channels/media_player.py b/homeassistant/components/channels/media_player.py index fb5f8cb6ac0..65be051ad17 100644 --- a/homeassistant/components/channels/media_player.py +++ b/homeassistant/components/channels/media_player.py @@ -4,7 +4,7 @@ import logging from pychannels import Channels import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, @@ -116,7 +116,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ChannelsPlayer(MediaPlayerDevice): +class ChannelsPlayer(MediaPlayerEntity): """Representation of a Channels instance.""" def __init__(self, name, host, port): diff --git a/homeassistant/components/clementine/media_player.py b/homeassistant/components/clementine/media_player.py index db4dfc38664..7478c9a7f2b 100644 --- a/homeassistant/components/clementine/media_player.py +++ b/homeassistant/components/clementine/media_player.py @@ -6,7 +6,7 @@ import time from clementineremote import ClementineRemote import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -67,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ClementineDevice(client, config[CONF_NAME])]) -class ClementineDevice(MediaPlayerDevice): +class ClementineDevice(MediaPlayerEntity): """Representation of Clementine Player.""" def __init__(self, client, name): diff --git a/homeassistant/components/cmus/media_player.py b/homeassistant/components/cmus/media_player.py index e9b9513479f..73a55fda8e3 100644 --- a/homeassistant/components/cmus/media_player.py +++ b/homeassistant/components/cmus/media_player.py @@ -4,7 +4,7 @@ import logging from pycmus import exceptions, remote import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -72,7 +72,7 @@ def setup_platform(hass, config, add_entities, discover_info=None): add_entities([cmus_remote], True) -class CmusDevice(MediaPlayerDevice): +class CmusDevice(MediaPlayerEntity): """Representation of a running cmus.""" # pylint: disable=no-member diff --git a/homeassistant/components/demo/media_player.py b/homeassistant/components/demo/media_player.py index 7a8f4eb8fbe..9cfb5582acc 100644 --- a/homeassistant/components/demo/media_player.py +++ b/homeassistant/components/demo/media_player.py @@ -1,5 +1,5 @@ """Demo implementation of the media player.""" -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, @@ -93,7 +93,7 @@ NETFLIX_PLAYER_SUPPORT = ( ) -class AbstractDemoPlayer(MediaPlayerDevice): +class AbstractDemoPlayer(MediaPlayerEntity): """A demo media players.""" # We only implement the methods that we support diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index 1fbd4885f43..9f451ab3025 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -4,7 +4,7 @@ import telnetlib import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -86,7 +86,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([denon]) -class DenonDevice(MediaPlayerDevice): +class DenonDevice(MediaPlayerEntity): """Representation of a Denon device.""" def __init__(self, name, host): diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index 7713354b14a..524e728588b 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -6,7 +6,7 @@ import logging import denonavr import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MUSIC, @@ -159,7 +159,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(receivers) -class DenonDevice(MediaPlayerDevice): +class DenonDevice(MediaPlayerEntity): """Representation of a Denon Media Player Device.""" def __init__(self, receiver): diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index ec39734573b..205503fe17f 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -4,7 +4,7 @@ from typing import Callable, List from directv import DIRECTV -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, @@ -77,7 +77,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerDevice): +class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerEntity): """Representation of a DirecTV receiver on the network.""" def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None: @@ -141,7 +141,7 @@ class DIRECTVMediaPlayer(DIRECTVEntity, MediaPlayerDevice): return self._address - # MediaPlayerDevice properties and methods + # MediaPlayerEntity properties and methods @property def state(self): """Return the state of the device.""" diff --git a/homeassistant/components/dlna_dmr/media_player.py b/homeassistant/components/dlna_dmr/media_player.py index 1e3ba840d6f..75d88d59c32 100644 --- a/homeassistant/components/dlna_dmr/media_player.py +++ b/homeassistant/components/dlna_dmr/media_player.py @@ -11,7 +11,7 @@ from async_upnp_client.aiohttp import AiohttpNotifyServer, AiohttpSessionRequest from async_upnp_client.profiles.dlna import DeviceState, DmrDevice import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, @@ -194,7 +194,7 @@ async def async_setup_platform( async_add_entities([device], True) -class DlnaDmrDevice(MediaPlayerDevice): +class DlnaDmrDevice(MediaPlayerEntity): """Representation of a DLNA DMR device.""" def __init__(self, dmr_device, name=None): diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index bb32bff2a15..7ef0171dd6c 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -2,7 +2,7 @@ from pdunehd import DuneHDPlayer import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -55,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([DuneHDPlayerEntity(DuneHDPlayer(host), name, sources)], True) -class DuneHDPlayerEntity(MediaPlayerDevice): +class DuneHDPlayerEntity(MediaPlayerEntity): """Implementation of the Dune HD player.""" def __init__(self, player, name, sources): diff --git a/homeassistant/components/emby/media_player.py b/homeassistant/components/emby/media_player.py index 0a14799ce24..7872b8215a6 100644 --- a/homeassistant/components/emby/media_player.py +++ b/homeassistant/components/emby/media_player.py @@ -4,7 +4,7 @@ import logging from pyemby import EmbyServer import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, @@ -134,7 +134,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_emby) -class EmbyDevice(MediaPlayerDevice): +class EmbyDevice(MediaPlayerEntity): """Representation of an Emby device.""" def __init__(self, emby, device_id): diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index f8a341481a8..563b2c5195d 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -4,7 +4,7 @@ import logging from openwebif.api import CreateDevice import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_TVSHOW, SUPPORT_NEXT_TRACK, @@ -117,7 +117,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices([Enigma2Device(config[CONF_NAME], device)], True) -class Enigma2Device(MediaPlayerDevice): +class Enigma2Device(MediaPlayerEntity): """Representation of an Enigma2 box.""" def __init__(self, name, device): diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index 6a04988bebb..df0dcc536b5 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -26,7 +26,7 @@ from epson_projector.const import ( ) import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PREVIOUS_TRACK, @@ -124,7 +124,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class EpsonProjector(MediaPlayerDevice): +class EpsonProjector(MediaPlayerEntity): """Representation of Epson Projector Device.""" def __init__(self, websession, name, host, port, encryption): diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 93e96d6e967..852528fb3a5 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -5,7 +5,7 @@ from afsapi import AFSAPI import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -94,7 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return False -class AFSAPIDevice(MediaPlayerDevice): +class AFSAPIDevice(MediaPlayerEntity): """Representation of a Frontier Silicon device on the network.""" def __init__(self, device_url, password, name): diff --git a/homeassistant/components/gpmdp/media_player.py b/homeassistant/components/gpmdp/media_player.py index e7b18aacc15..2fa227f0953 100644 --- a/homeassistant/components/gpmdp/media_player.py +++ b/homeassistant/components/gpmdp/media_player.py @@ -7,7 +7,7 @@ import time import voluptuous as vol from websocket import _exceptions, create_connection -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -167,7 +167,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): setup_gpmdp(hass, config, code, add_entities) -class GPMDP(MediaPlayerDevice): +class GPMDP(MediaPlayerEntity): """Representation of a GPMDP.""" def __init__(self, name, url, code): diff --git a/homeassistant/components/gstreamer/media_player.py b/homeassistant/components/gstreamer/media_player.py index 9b371bfffca..ea211ccd748 100644 --- a/homeassistant/components/gstreamer/media_player.py +++ b/homeassistant/components/gstreamer/media_player.py @@ -4,7 +4,7 @@ import logging from gsp import GstreamerPlayer import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -50,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([GstreamerDevice(player, name)]) -class GstreamerDevice(MediaPlayerDevice): +class GstreamerDevice(MediaPlayerEntity): """Representation of a Gstreamer device.""" def __init__(self, player, name): diff --git a/homeassistant/components/harman_kardon_avr/media_player.py b/homeassistant/components/harman_kardon_avr/media_player.py index fd7cddcaed9..30a857f8b99 100644 --- a/homeassistant/components/harman_kardon_avr/media_player.py +++ b/homeassistant/components/harman_kardon_avr/media_player.py @@ -4,7 +4,7 @@ import logging import hkavr import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -49,7 +49,7 @@ def setup_platform(hass, config, add_entities, discover_info=None): add_entities([avr_device], True) -class HkAvrDevice(MediaPlayerDevice): +class HkAvrDevice(MediaPlayerEntity): """Representation of a Harman Kardon AVR / JBL AVR TV.""" def __init__(self, avr): diff --git a/homeassistant/components/hdmi_cec/media_player.py b/homeassistant/components/hdmi_cec/media_player.py index c49ee45271a..180580ef371 100644 --- a/homeassistant/components/hdmi_cec/media_player.py +++ b/homeassistant/components/hdmi_cec/media_player.py @@ -22,7 +22,7 @@ from pycec.const import ( TYPE_TUNER, ) -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_NEXT_TRACK, @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class CecPlayerDevice(CecDevice, MediaPlayerDevice): +class CecPlayerDevice(CecDevice, MediaPlayerEntity): """Representation of a HDMI device as a Media player.""" def __init__(self, device, logical) -> None: diff --git a/homeassistant/components/heos/media_player.py b/homeassistant/components/heos/media_player.py index 39c5a9928af..7e827c96f55 100644 --- a/homeassistant/components/heos/media_player.py +++ b/homeassistant/components/heos/media_player.py @@ -6,7 +6,7 @@ from typing import Sequence from pyheos import HeosError, const as heos_const -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, DOMAIN, @@ -85,7 +85,7 @@ def log_command_error(command: str): return decorator -class HeosMediaPlayer(MediaPlayerDevice): +class HeosMediaPlayer(MediaPlayerEntity): """The HEOS player.""" def __init__(self, player): diff --git a/homeassistant/components/homekit_controller/media_player.py b/homeassistant/components/homekit_controller/media_player.py index 3d5e194ed94..249b8c9c3e0 100644 --- a/homeassistant/components/homekit_controller/media_player.py +++ b/homeassistant/components/homekit_controller/media_player.py @@ -10,7 +10,7 @@ from aiohomekit.model.characteristics import ( from aiohomekit.model.services import ServicesTypes from aiohomekit.utils import clamp_enum_to_char -from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerDevice +from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_PAUSE, SUPPORT_PLAY, @@ -54,7 +54,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): conn.add_listener(async_add_service) -class HomeKitTelevision(HomeKitEntity, MediaPlayerDevice): +class HomeKitTelevision(HomeKitEntity, MediaPlayerEntity): """Representation of a HomeKit Controller Television.""" def get_characteristic_types(self): diff --git a/homeassistant/components/horizon/media_player.py b/homeassistant/components/horizon/media_player.py index 44e93d26a40..79a94538b70 100644 --- a/homeassistant/components/horizon/media_player.py +++ b/homeassistant/components/horizon/media_player.py @@ -7,7 +7,7 @@ from horimote.exceptions import AuthenticationError import voluptuous as vol from homeassistant import util -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([HorizonDevice(client, name, keys)], True) -class HorizonDevice(MediaPlayerDevice): +class HorizonDevice(MediaPlayerEntity): """Representation of a Horizon HD Recorder.""" def __init__(self, client, name, remote_keys): diff --git a/homeassistant/components/itunes/media_player.py b/homeassistant/components/itunes/media_player.py index e96c40b13b6..707cac6f953 100644 --- a/homeassistant/components/itunes/media_player.py +++ b/homeassistant/components/itunes/media_player.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -205,7 +205,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ItunesDevice(MediaPlayerDevice): +class ItunesDevice(MediaPlayerEntity): """Representation of an iTunes API instance.""" def __init__(self, name, host, port, use_ssl, add_entities): @@ -408,7 +408,7 @@ class ItunesDevice(MediaPlayerDevice): self.update_state(response) -class AirPlayDevice(MediaPlayerDevice): +class AirPlayDevice(MediaPlayerEntity): """Representation an AirPlay device via an iTunes API instance.""" def __init__(self, device_id, client): diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index d888b30a3b9..cf87a7dd447 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -23,7 +23,7 @@ from homeassistant.components.media_player import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, - MediaPlayerDevice, + MediaPlayerEntity, ) from homeassistant.const import ( CONF_HOST, @@ -174,7 +174,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= add_service(SERVICE_SUB_DB, "sub_db", "db_value") -class KefMediaPlayer(MediaPlayerDevice): +class KefMediaPlayer(MediaPlayerEntity): """Kef Player Object.""" def __init__( diff --git a/homeassistant/components/kodi/__init__.py b/homeassistant/components/kodi/__init__.py index 9470c8bb2c8..094bdf0984b 100644 --- a/homeassistant/components/kodi/__init__.py +++ b/homeassistant/components/kodi/__init__.py @@ -49,7 +49,7 @@ async def async_setup(hass, config): if any((CONF_PLATFORM, DOMAIN) in cfg.items() for cfg in config.get(MP_DOMAIN, [])): # Register the Kodi media_player services async def async_service_handler(service): - """Map services to methods on MediaPlayerDevice.""" + """Map services to methods on MediaPlayerEntity.""" method = SERVICE_TO_METHOD.get(service.service) if not method: return diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index 33e7c014e40..aea45a199a4 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -14,7 +14,7 @@ import voluptuous as vol from homeassistant.components.kodi import SERVICE_CALL_METHOD from homeassistant.components.kodi.const import DOMAIN -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, MEDIA_TYPE_MOVIE, @@ -248,7 +248,7 @@ def cmd(func): return wrapper -class KodiDevice(MediaPlayerDevice): +class KodiDevice(MediaPlayerEntity): """Representation of a XBMC/Kodi device.""" def __init__( diff --git a/homeassistant/components/lg_netcast/media_player.py b/homeassistant/components/lg_netcast/media_player.py index cb91257f83d..d4e5fc119ac 100644 --- a/homeassistant/components/lg_netcast/media_player.py +++ b/homeassistant/components/lg_netcast/media_player.py @@ -7,7 +7,7 @@ from requests import RequestException import voluptuous as vol from homeassistant import util -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -75,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([LgTVDevice(client, name, on_action_script)], True) -class LgTVDevice(MediaPlayerDevice): +class LgTVDevice(MediaPlayerEntity): """Representation of a LG TV.""" def __init__(self, client, name, on_action_script): diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index 30cfbf17074..f3c89d6138e 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -3,7 +3,7 @@ import logging import temescal -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOUND_MODE, SUPPORT_SELECT_SOURCE, @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([LGDevice(discovery_info)], True) -class LGDevice(MediaPlayerDevice): +class LGDevice(MediaPlayerEntity): """Representation of an LG soundbar device.""" def __init__(self, discovery_info): diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index a8c87effb10..fe970189e5c 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -337,8 +337,8 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class MediaPlayerDevice(Entity): - """ABC for media player devices.""" +class MediaPlayerEntity(Entity): + """ABC for media player entities.""" _access_token: Optional[str] = None @@ -924,3 +924,15 @@ async def websocket_handle_thumbnail(hass, connection, msg): "content": base64.b64encode(data).decode("utf-8"), }, ) + + +class MediaPlayerDevice(MediaPlayerEntity): + """ABC for media player devices (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "MediaPlayerDevice is deprecated, modify %s to extend MediaPlayerEntity", + cls.__name__, + ) diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py index 492c347959e..abbfa21761f 100644 --- a/homeassistant/components/mediaroom/media_player.py +++ b/homeassistant/components/mediaroom/media_player.py @@ -4,7 +4,7 @@ import logging from pymediaroom import PyMediaroomError, Remote, State, install_mediaroom_protocol import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -118,7 +118,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _LOGGER.debug("Auto discovery installed") -class MediaroomDevice(MediaPlayerDevice): +class MediaroomDevice(MediaPlayerEntity): """Representation of a Mediaroom set-up-box on the network.""" def set_state(self, mediaroom_state): diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py index 9073ab224f1..855d1b41f98 100644 --- a/homeassistant/components/monoprice/media_player.py +++ b/homeassistant/components/monoprice/media_player.py @@ -4,7 +4,7 @@ import logging from serial import SerialException from homeassistant import core -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -107,7 +107,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MonopriceZone(MediaPlayerDevice): +class MonopriceZone(MediaPlayerEntity): """Representation of a Monoprice amplifier zone.""" def __init__(self, monoprice, sources, namespace, zone_id): diff --git a/homeassistant/components/mpchc/media_player.py b/homeassistant/components/mpchc/media_player.py index b69fa651988..c09ab685597 100644 --- a/homeassistant/components/mpchc/media_player.py +++ b/homeassistant/components/mpchc/media_player.py @@ -5,7 +5,7 @@ import re import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([MpcHcDevice(name, url)], True) -class MpcHcDevice(MediaPlayerDevice): +class MpcHcDevice(MediaPlayerEntity): """Representation of a MPC-HC server.""" def __init__(self, name, url): diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index bec61b10a9f..ba9b2f73d3c 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -6,7 +6,7 @@ import os import mpd import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -81,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([device], True) -class MpdDevice(MediaPlayerDevice): +class MpdDevice(MediaPlayerEntity): """Representation of a MPD server.""" # pylint: disable=no-member diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 0c29aac427f..c9015b85fd1 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -4,7 +4,7 @@ import logging from nad_receiver import NADReceiver, NADReceiverTCP, NADReceiverTelnet import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -105,7 +105,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class NAD(MediaPlayerDevice): +class NAD(MediaPlayerEntity): """Representation of a NAD Receiver.""" def __init__(self, name, nad_receiver, min_volume, max_volume, source_dict): @@ -221,7 +221,7 @@ class NAD(MediaPlayerDevice): ) -class NADtcp(MediaPlayerDevice): +class NADtcp(MediaPlayerEntity): """Representation of a NAD Digital amplifier.""" def __init__(self, name, nad_device, min_volume, max_volume, volume_step): diff --git a/homeassistant/components/onkyo/media_player.py b/homeassistant/components/onkyo/media_player.py index 93107b2eb48..30f4ae0800a 100644 --- a/homeassistant/components/onkyo/media_player.py +++ b/homeassistant/components/onkyo/media_player.py @@ -6,7 +6,7 @@ import eiscp from eiscp import eISCP import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( DOMAIN, SUPPORT_PLAY, @@ -211,7 +211,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(hosts, True) -class OnkyoDevice(MediaPlayerDevice): +class OnkyoDevice(MediaPlayerEntity): """Representation of an Onkyo device.""" def __init__( diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index 8456ae9338d..4225228e271 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -3,7 +3,7 @@ import logging from openhomedevice.Device import Device -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class OpenhomeDevice(MediaPlayerDevice): +class OpenhomeDevice(MediaPlayerEntity): """Representation of an Openhome device.""" def __init__(self, hass, device): diff --git a/homeassistant/components/panasonic_bluray/media_player.py b/homeassistant/components/panasonic_bluray/media_player.py index 4a816252580..ddbd9f6670b 100644 --- a/homeassistant/components/panasonic_bluray/media_player.py +++ b/homeassistant/components/panasonic_bluray/media_player.py @@ -5,7 +5,7 @@ import logging from panacotta import PanasonicBD import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_PAUSE, SUPPORT_PLAY, @@ -49,7 +49,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([PanasonicBluRay(conf[CONF_HOST], conf[CONF_NAME])]) -class PanasonicBluRay(MediaPlayerDevice): +class PanasonicBluRay(MediaPlayerEntity): """Representation of a Panasonic Blu-ray device.""" def __init__(self, ip, name): diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index abfb10e11f3..ce1d3762693 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -5,7 +5,7 @@ from urllib.request import URLError from panasonic_viera import EncryptionRequired, Keys, RemoteControl, SOAPError -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_URL, SUPPORT_NEXT_TRACK, @@ -68,7 +68,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([tv_device]) -class PanasonicVieraTVDevice(MediaPlayerDevice): +class PanasonicVieraTVDevice(MediaPlayerEntity): """Representation of a Panasonic Viera TV.""" def __init__( diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py index 322765ac082..459e583c267 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -9,7 +9,7 @@ import signal import pexpect from homeassistant import util -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -72,12 +72,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([pandora]) -class PandoraMediaPlayer(MediaPlayerDevice): +class PandoraMediaPlayer(MediaPlayerEntity): """A media player that uses the Pianobar interface to Pandora.""" def __init__(self, name): """Initialize the Pandora device.""" - MediaPlayerDevice.__init__(self) self._name = name self._player_state = STATE_OFF self._station = "" diff --git a/homeassistant/components/philips_js/media_player.py b/homeassistant/components/philips_js/media_player.py index fe6d7edf804..f72aef2c464 100644 --- a/homeassistant/components/philips_js/media_player.py +++ b/homeassistant/components/philips_js/media_player.py @@ -5,7 +5,7 @@ import logging from haphilipsjs import PhilipsTV import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -82,7 +82,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([PhilipsTVMediaPlayer(tvapi, name, on_script)]) -class PhilipsTVMediaPlayer(MediaPlayerDevice): +class PhilipsTVMediaPlayer(MediaPlayerEntity): """Representation of a Philips TV exposing the JointSpace API.""" def __init__(self, tv, name, on_script): diff --git a/homeassistant/components/pjlink/media_player.py b/homeassistant/components/pjlink/media_player.py index e93e6e5fb20..6316ea421e0 100644 --- a/homeassistant/components/pjlink/media_player.py +++ b/homeassistant/components/pjlink/media_player.py @@ -5,7 +5,7 @@ from pypjlink import MUTE_AUDIO, Projector from pypjlink.projector import ProjectorError import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -70,7 +70,7 @@ def format_input_source(input_source_name, input_source_number): return f"{input_source_name} {input_source_number}" -class PjLinkDevice(MediaPlayerDevice): +class PjLinkDevice(MediaPlayerEntity): """Representation of a PJLink device.""" def __init__(self, host, port, name, encoding, password): diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index 79d1339bde1..b19f687482c 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -5,7 +5,7 @@ import logging import plexapi.exceptions import requests.exceptions -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerDevice +from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, @@ -88,7 +88,7 @@ def _async_add_entities( async_add_entities(entities, True) -class PlexMediaPlayer(MediaPlayerDevice): +class PlexMediaPlayer(MediaPlayerEntity): """Representation of a Plex device.""" def __init__(self, plex_server, device, session=None): diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 3acaf75d699..39b60be0493 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -5,7 +5,7 @@ import logging from pyps4_2ndscreen.errors import NotReady, PSDataIncomplete import pyps4_2ndscreen.ps4 as pyps4 -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_TITLE, @@ -69,7 +69,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(device_list, update_before_add=True) -class PS4Device(MediaPlayerDevice): +class PS4Device(MediaPlayerEntity): """Representation of a PS4.""" def __init__(self, config, name, host, region, ps4, creds): diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 950946831a8..0a71680a03c 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,7 +7,7 @@ from requests.exceptions import ( ) from roku import RokuException -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -45,7 +45,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([RokuDevice(roku)], True) -class RokuDevice(MediaPlayerDevice): +class RokuDevice(MediaPlayerEntity): """Representation of a Roku device on the network.""" def __init__(self, roku): diff --git a/homeassistant/components/russound_rio/media_player.py b/homeassistant/components/russound_rio/media_player.py index a1fe057d9fb..d65ef1ce0e5 100644 --- a/homeassistant/components/russound_rio/media_player.py +++ b/homeassistant/components/russound_rio/media_player.py @@ -4,7 +4,7 @@ import logging from russound_rio import Russound import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_SELECT_SOURCE, @@ -73,7 +73,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class RussoundZoneDevice(MediaPlayerDevice): +class RussoundZoneDevice(MediaPlayerEntity): """Representation of a Russound Zone.""" def __init__(self, russ, zone_id, name, sources): diff --git a/homeassistant/components/russound_rnet/media_player.py b/homeassistant/components/russound_rnet/media_player.py index 70ed1212363..e1a6430c9cc 100644 --- a/homeassistant/components/russound_rnet/media_player.py +++ b/homeassistant/components/russound_rnet/media_player.py @@ -4,7 +4,7 @@ import logging from russound import russound import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -68,7 +68,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Not connected to %s:%s", host, port) -class RussoundRNETDevice(MediaPlayerDevice): +class RussoundRNETDevice(MediaPlayerEntity): """Representation of a Russound RNET device.""" def __init__(self, hass, russ, sources, zone_id, extra): diff --git a/homeassistant/components/samsungtv/media_player.py b/homeassistant/components/samsungtv/media_player.py index 35a374688b5..7eb0f50efc2 100644 --- a/homeassistant/components/samsungtv/media_player.py +++ b/homeassistant/components/samsungtv/media_player.py @@ -4,7 +4,7 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerDevice +from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -81,7 +81,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([SamsungTVDevice(bridge, config_entry, on_script)]) -class SamsungTVDevice(MediaPlayerDevice): +class SamsungTVDevice(MediaPlayerEntity): """Representation of a Samsung TV.""" def __init__(self, bridge, config_entry, on_script): diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index 103ec694d83..dbc350453b7 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -4,7 +4,7 @@ import logging import aiohttp from sisyphus_control import Track -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -56,7 +56,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SisyphusPlayer(table_holder.name, host, table)], True) -class SisyphusPlayer(MediaPlayerDevice): +class SisyphusPlayer(MediaPlayerEntity): """Representation of a Sisyphus table as a media player device.""" def __init__(self, name, host, table): diff --git a/homeassistant/components/snapcast/media_player.py b/homeassistant/components/snapcast/media_player.py index 1bd44959ab1..ab4b2415034 100644 --- a/homeassistant/components/snapcast/media_player.py +++ b/homeassistant/components/snapcast/media_player.py @@ -6,7 +6,7 @@ import snapcast.control from snapcast.control.server import CONTROL_PORT import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_VOLUME_MUTE, @@ -110,7 +110,7 @@ async def handle_set_latency(entity, service_call): await entity.async_set_latency(service_call.data[ATTR_LATENCY]) -class SnapcastGroupDevice(MediaPlayerDevice): +class SnapcastGroupDevice(MediaPlayerEntity): """Representation of a Snapcast group device.""" def __init__(self, group, uid_part): @@ -200,7 +200,7 @@ class SnapcastGroupDevice(MediaPlayerDevice): await self._group.restore() -class SnapcastClientDevice(MediaPlayerDevice): +class SnapcastClientDevice(MediaPlayerEntity): """Representation of a Snapcast client device.""" def __init__(self, client, uid_part): diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index d11ff84a73c..e2a9d9c5d57 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -13,7 +13,7 @@ from songpal import ( ) import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, @@ -117,7 +117,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SongpalDevice(MediaPlayerDevice): +class SongpalDevice(MediaPlayerEntity): """Class representing a Songpal device.""" def __init__(self, name, endpoint, poll=False): diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 634952dcfdc..55107101610 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -13,7 +13,7 @@ import pysonos.music_library import pysonos.snapshot import voluptuous as vol -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, @@ -338,7 +338,7 @@ def _timespan_secs(timespan): return sum(60 ** x[0] * int(x[1]) for x in enumerate(reversed(timespan.split(":")))) -class SonosEntity(MediaPlayerDevice): +class SonosEntity(MediaPlayerEntity): """Representation of a Sonos entity.""" def __init__(self, player): diff --git a/homeassistant/components/soundtouch/media_player.py b/homeassistant/components/soundtouch/media_player.py index 2f64a2d3605..482d1025034 100644 --- a/homeassistant/components/soundtouch/media_player.py +++ b/homeassistant/components/soundtouch/media_player.py @@ -5,7 +5,7 @@ import re from libsoundtouch import soundtouch_device import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -184,7 +184,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class SoundTouchDevice(MediaPlayerDevice): +class SoundTouchDevice(MediaPlayerEntity): """Representation of a SoundTouch Bose device.""" def __init__(self, name, config): diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 7a00fb02146..c708a9fc27e 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -9,7 +9,7 @@ from aiohttp import ClientError from spotipy import Spotify, SpotifyException from yarl import URL -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, @@ -90,7 +90,7 @@ def spotify_exception_handler(func): return wrapper -class SpotifyMediaPlayer(MediaPlayerDevice): +class SpotifyMediaPlayer(MediaPlayerEntity): """Representation of a Spotify controller.""" def __init__(self, session, spotify: Spotify, me: dict, user_id: str, name: str): diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 57305a3b4c0..f40374d9486 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -9,7 +9,7 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_MEDIA_ENQUEUE, MEDIA_TYPE_MUSIC, @@ -147,7 +147,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(players) async def async_service_handler(service): - """Map services to methods on MediaPlayerDevice.""" + """Map services to methods on MediaPlayerEntity.""" method = SERVICE_TO_METHOD.get(service.service) if not method: return @@ -245,7 +245,7 @@ class LogitechMediaServer: return False -class SqueezeBoxDevice(MediaPlayerDevice): +class SqueezeBoxDevice(MediaPlayerEntity): """Representation of a SqueezeBox device.""" def __init__(self, lms, player_id, name): diff --git a/homeassistant/components/ue_smart_radio/media_player.py b/homeassistant/components/ue_smart_radio/media_player.py index d25c52608e1..9f6d01cfb5d 100644 --- a/homeassistant/components/ue_smart_radio/media_player.py +++ b/homeassistant/components/ue_smart_radio/media_player.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -88,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([UERadioDevice(session, player_id, player_name)]) -class UERadioDevice(MediaPlayerDevice): +class UERadioDevice(MediaPlayerEntity): """Representation of a Logitech UE Smart Radio device.""" def __init__(self, session, player_id, player_name): diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index 803793d0683..f1cad7e8abf 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( ATTR_APP_ID, ATTR_APP_NAME, @@ -116,7 +116,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([player]) -class UniversalMediaPlayer(MediaPlayerDevice): +class UniversalMediaPlayer(MediaPlayerEntity): """Representation of an universal media player.""" def __init__(self, hass, name, children, commands, attributes, state_template=None): diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index bb7ae3f75b0..2a81ed1eaad 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -10,7 +10,7 @@ from pyvizio.const import APP_HOME, APPS, INPUT_APPS, NO_APP_RUNNING, UNKNOWN_AP from homeassistant.components.media_player import ( DEVICE_CLASS_SPEAKER, SUPPORT_SELECT_SOUND_MODE, - MediaPlayerDevice, + MediaPlayerEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -114,7 +114,7 @@ async def async_setup_entry( async_add_entities([entity], update_before_add=True) -class VizioDevice(MediaPlayerDevice): +class VizioDevice(MediaPlayerEntity): """Media Player implementation which performs REST requests to device.""" def __init__( diff --git a/homeassistant/components/vlc/media_player.py b/homeassistant/components/vlc/media_player.py index c7a3d49fabc..fe387dd4adc 100644 --- a/homeassistant/components/vlc/media_player.py +++ b/homeassistant/components/vlc/media_player.py @@ -4,7 +4,7 @@ import logging import vlc import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class VlcDevice(MediaPlayerDevice): +class VlcDevice(MediaPlayerEntity): """Representation of a vlc player.""" def __init__(self, name, arguments): diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index 45b0971ad9f..1f0d62b6ee8 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -4,7 +4,7 @@ import logging from python_telnet_vlc import ConnectionError as ConnErr, VLCTelnet import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, @@ -76,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class VlcDevice(MediaPlayerDevice): +class VlcDevice(MediaPlayerEntity): """Representation of a vlc player.""" def __init__(self, name, host, port, passwd): diff --git a/homeassistant/components/volumio/media_player.py b/homeassistant/components/volumio/media_player.py index 6d46b6015e0..8d50f56bd65 100644 --- a/homeassistant/components/volumio/media_player.py +++ b/homeassistant/components/volumio/media_player.py @@ -11,7 +11,7 @@ import socket import aiohttp import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_CLEAR_PLAYLIST, @@ -104,7 +104,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([entity]) -class Volumio(MediaPlayerDevice): +class Volumio(MediaPlayerEntity): """Volumio Player Object.""" def __init__(self, name, host, port, hass): diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index a19f42d7d56..16dc98e5eac 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -8,7 +8,7 @@ from aiopylgtv import PyLGTVCmdException, PyLGTVPairException, WebOsClient from websockets.exceptions import ConnectionClosed from homeassistant import util -from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerDevice +from homeassistant.components.media_player import DEVICE_CLASS_TV, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, @@ -109,7 +109,7 @@ def cmd(func): return wrapper -class LgWebOSMediaPlayerEntity(MediaPlayerDevice): +class LgWebOSMediaPlayerEntity(MediaPlayerEntity): """Representation of a LG webOS Smart TV.""" def __init__(self, client: WebOsClient, name: str, customize, on_script=None): diff --git a/homeassistant/components/xiaomi_tv/media_player.py b/homeassistant/components/xiaomi_tv/media_player.py index c82708852c2..c247559766b 100644 --- a/homeassistant/components/xiaomi_tv/media_player.py +++ b/homeassistant/components/xiaomi_tv/media_player.py @@ -4,7 +4,7 @@ import logging import pymitv import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(XiaomiTV(tv, DEFAULT_NAME) for tv in pymitv.Discover().scan()) -class XiaomiTV(MediaPlayerDevice): +class XiaomiTV(MediaPlayerEntity): """Represent the Xiaomi TV for Home Assistant.""" def __init__(self, ip, name): diff --git a/homeassistant/components/yamaha/media_player.py b/homeassistant/components/yamaha/media_player.py index 5aa0299200a..b26729c720e 100644 --- a/homeassistant/components/yamaha/media_player.py +++ b/homeassistant/components/yamaha/media_player.py @@ -5,7 +5,7 @@ import requests import rxv import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -153,7 +153,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class YamahaDevice(MediaPlayerDevice): +class YamahaDevice(MediaPlayerEntity): """Representation of a Yamaha device.""" def __init__(self, name, receiver, source_ignore, source_names, zone_names): diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index f239e07a1dc..32f223f6728 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -5,7 +5,7 @@ import socket import pymusiccast import voluptuous as vol -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, @@ -105,7 +105,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): known_hosts.remove(reg_host) -class YamahaDevice(MediaPlayerDevice): +class YamahaDevice(MediaPlayerEntity): """Representation of a Yamaha MusicCast device.""" def __init__(self, recv, zone): diff --git a/homeassistant/components/ziggo_mediabox_xl/media_player.py b/homeassistant/components/ziggo_mediabox_xl/media_player.py index 832758e26fb..d3493f0dc35 100644 --- a/homeassistant/components/ziggo_mediabox_xl/media_player.py +++ b/homeassistant/components/ziggo_mediabox_xl/media_player.py @@ -5,7 +5,7 @@ import socket import voluptuous as vol from ziggo_mediabox_xl import ZiggoMediaboxXL -from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice +from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -92,7 +92,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(hosts, True) -class ZiggoMediaboxXLDevice(MediaPlayerDevice): +class ZiggoMediaboxXLDevice(MediaPlayerEntity): """Representation of a Ziggo Mediabox XL Device.""" def __init__(self, mediabox, host, name, available): diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index 2cbca449ed6..ac0d70bded9 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -14,7 +14,7 @@ from homeassistant.const import ( from tests.common import get_test_home_assistant -class AsyncMediaPlayer(mp.MediaPlayerDevice): +class AsyncMediaPlayer(mp.MediaPlayerEntity): """Async media player test class.""" def __init__(self, hass): @@ -65,7 +65,7 @@ class AsyncMediaPlayer(mp.MediaPlayerDevice): self._state = STATE_OFF -class SyncMediaPlayer(mp.MediaPlayerDevice): +class SyncMediaPlayer(mp.MediaPlayerEntity): """Sync media player test class.""" def __init__(self, hass): diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index f3d8ec3298a..330581d2d14 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -3,6 +3,7 @@ import base64 from asynctest import patch +from homeassistant.components import media_player from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.setup import async_setup_component @@ -18,7 +19,7 @@ async def test_get_image(hass, hass_ws_client, caplog): client = await hass_ws_client(hass) with patch( - "homeassistant.components.media_player.MediaPlayerDevice." + "homeassistant.components.media_player.MediaPlayerEntity." "async_get_media_image", return_value=mock_coro((b"image", "image/jpeg")), ): @@ -53,7 +54,7 @@ async def test_get_image_http(hass, aiohttp_client): client = await aiohttp_client(hass.http.app) with patch( - "homeassistant.components.media_player.MediaPlayerDevice." + "homeassistant.components.media_player.MediaPlayerEntity." "async_get_media_image", return_value=(b"image", "image/jpeg"), ): @@ -66,7 +67,7 @@ async def test_get_image_http(hass, aiohttp_client): async def test_get_image_http_remote(hass, aiohttp_client): """Test get image url via http command.""" with patch( - "homeassistant.components.media_player.MediaPlayerDevice." + "homeassistant.components.media_player.MediaPlayerEntity." "media_image_remotely_accessible", return_value=True, ): @@ -80,7 +81,7 @@ async def test_get_image_http_remote(hass, aiohttp_client): client = await aiohttp_client(hass.http.app) with patch( - "homeassistant.components.media_player.MediaPlayerDevice." + "homeassistant.components.media_player.MediaPlayerEntity." "async_get_media_image", return_value=(b"image", "image/jpeg"), ): @@ -88,3 +89,13 @@ async def test_get_image_http_remote(hass, aiohttp_client): content = await resp.read() assert content == b"image" + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomMediaPlayer(media_player.MediaPlayerDevice): + pass + + CustomMediaPlayer() + assert "MediaPlayerDevice is deprecated, modify CustomMediaPlayer" in caplog.text diff --git a/tests/components/universal/test_media_player.py b/tests/components/universal/test_media_player.py index cf3fc8fcb33..bf780d33922 100644 --- a/tests/components/universal/test_media_player.py +++ b/tests/components/universal/test_media_player.py @@ -22,7 +22,7 @@ def validate_config(config): return validated_config -class MockMediaPlayer(media_player.MediaPlayerDevice): +class MockMediaPlayer(media_player.MediaPlayerEntity): """Mock media player for testing.""" def __init__(self, hass, name): From b30d117e7d588ee31df882f7be9f0078edffe0b2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 25 Apr 2020 18:02:41 +0200 Subject: [PATCH 055/511] Rename LockDevice to LockEntity (#34594) --- homeassistant/components/abode/lock.py | 4 ++-- homeassistant/components/august/lock.py | 4 ++-- .../components/bmw_connected_drive/lock.py | 4 ++-- homeassistant/components/demo/lock.py | 4 ++-- .../components/homekit_controller/lock.py | 4 ++-- homeassistant/components/homematic/lock.py | 4 ++-- homeassistant/components/isy994/lock.py | 4 ++-- homeassistant/components/keba/lock.py | 4 ++-- homeassistant/components/kiwi/lock.py | 4 ++-- homeassistant/components/lock/__init__.py | 15 ++++++++++++++- homeassistant/components/lockitron/lock.py | 4 ++-- homeassistant/components/mqtt/lock.py | 4 ++-- homeassistant/components/nello/lock.py | 4 ++-- homeassistant/components/nuki/lock.py | 4 ++-- homeassistant/components/sesame/lock.py | 4 ++-- homeassistant/components/simplisafe/lock.py | 4 ++-- homeassistant/components/smartthings/lock.py | 4 ++-- homeassistant/components/starline/lock.py | 4 ++-- homeassistant/components/tahoma/lock.py | 4 ++-- homeassistant/components/template/lock.py | 4 ++-- homeassistant/components/tesla/lock.py | 4 ++-- homeassistant/components/vera/lock.py | 4 ++-- homeassistant/components/verisure/lock.py | 4 ++-- homeassistant/components/volvooncall/lock.py | 4 ++-- homeassistant/components/wink/lock.py | 4 ++-- homeassistant/components/xiaomi_aqara/lock.py | 4 ++-- homeassistant/components/zha/lock.py | 4 ++-- homeassistant/components/zwave/lock.py | 4 ++-- tests/components/lock/test_init.py | 12 ++++++++++++ .../testing_config/custom_components/test/lock.py | 4 ++-- 30 files changed, 82 insertions(+), 57 deletions(-) create mode 100644 tests/components/lock/test_init.py diff --git a/homeassistant/components/abode/lock.py b/homeassistant/components/abode/lock.py index 33431433ef9..2a52663c0e7 100644 --- a/homeassistant/components/abode/lock.py +++ b/homeassistant/components/abode/lock.py @@ -1,7 +1,7 @@ """Support for the Abode Security System locks.""" import abodepy.helpers.constants as CONST -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from . import AbodeDevice from .const import DOMAIN @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeLock(AbodeDevice, LockDevice): +class AbodeLock(AbodeDevice, LockEntity): """Representation of an Abode lock.""" def lock(self, **kwargs): diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 495c215edad..e16c603d919 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -5,7 +5,7 @@ from august.activity import ActivityType from august.lock import LockStatus from august.util import update_lock_detail_from_activity -from homeassistant.components.lock import ATTR_CHANGED_BY, LockDevice +from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.core import callback from homeassistant.helpers.restore_state import RestoreEntity @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(devices, True) -class AugustLock(AugustEntityMixin, RestoreEntity, LockDevice): +class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity): """Representation of an August lock.""" def __init__(self, data, device): diff --git a/homeassistant/components/bmw_connected_drive/lock.py b/homeassistant/components/bmw_connected_drive/lock.py index 7d4ad420af4..d30f1702ae8 100644 --- a/homeassistant/components/bmw_connected_drive/lock.py +++ b/homeassistant/components/bmw_connected_drive/lock.py @@ -3,7 +3,7 @@ import logging from bimmer_connected.state import LockState -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_ATTRIBUTION, STATE_LOCKED, STATE_UNLOCKED from . import DOMAIN as BMW_DOMAIN @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class BMWLock(LockDevice): +class BMWLock(LockEntity): """Representation of a BMW vehicle lock.""" def __init__(self, account, vehicle, attribute: str, sensor_name): diff --git a/homeassistant/components/demo/lock.py b/homeassistant/components/demo/lock.py index 5074741d83d..63f2d218957 100644 --- a/homeassistant/components/demo/lock.py +++ b/homeassistant/components/demo/lock.py @@ -1,5 +1,5 @@ """Demo lock platform that has two fake locks.""" -from homeassistant.components.lock import SUPPORT_OPEN, LockDevice +from homeassistant.components.lock import SUPPORT_OPEN, LockEntity from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoLock(LockDevice): +class DemoLock(LockEntity): """Representation of a Demo lock.""" def __init__(self, name, state, openable=False): diff --git a/homeassistant/components/homekit_controller/lock.py b/homeassistant/components/homekit_controller/lock.py index c07f85fb50f..93bb4f1568f 100644 --- a/homeassistant/components/homekit_controller/lock.py +++ b/homeassistant/components/homekit_controller/lock.py @@ -3,7 +3,7 @@ import logging from aiohomekit.model.characteristics import CharacteristicsTypes -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import callback @@ -34,7 +34,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): conn.add_listener(async_add_service) -class HomeKitLock(HomeKitEntity, LockDevice): +class HomeKitLock(HomeKitEntity, LockEntity): """Representation of a HomeKit Controller Lock.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/lock.py b/homeassistant/components/homematic/lock.py index 0094ecd2e81..9a705627fa4 100644 --- a/homeassistant/components/homematic/lock.py +++ b/homeassistant/components/homematic/lock.py @@ -1,7 +1,7 @@ """Support for Homematic locks.""" import logging -from homeassistant.components.lock import SUPPORT_OPEN, LockDevice +from homeassistant.components.lock import SUPPORT_OPEN, LockEntity from .const import ATTR_DISCOVER_DEVICES from .entity import HMDevice @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMLock(HMDevice, LockDevice): +class HMLock(HMDevice, LockEntity): """Representation of a Homematic lock aka KeyMatic.""" @property diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index 9ea7c9e1f6e..807027d4610 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -2,7 +2,7 @@ import logging from typing import Callable -from homeassistant.components.lock import DOMAIN, LockDevice +from homeassistant.components.lock import DOMAIN, LockEntity from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED from homeassistant.helpers.typing import ConfigType @@ -27,7 +27,7 @@ def setup_platform( add_entities(devices) -class ISYLockDevice(ISYDevice, LockDevice): +class ISYLockDevice(ISYDevice, LockEntity): """Representation of an ISY994 lock device.""" def __init__(self, node) -> None: diff --git a/homeassistant/components/keba/lock.py b/homeassistant/components/keba/lock.py index f69fbdddf20..385adf662be 100644 --- a/homeassistant/components/keba/lock.py +++ b/homeassistant/components/keba/lock.py @@ -1,7 +1,7 @@ """Support for KEBA charging station switch.""" import logging -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from . import DOMAIN @@ -19,7 +19,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(sensors) -class KebaLock(LockDevice): +class KebaLock(LockEntity): """The entity class for KEBA charging stations switch.""" def __init__(self, keba, name, entity_type): diff --git a/homeassistant/components/kiwi/lock.py b/homeassistant/components/kiwi/lock.py index 4a58f8f43c2..79971d5aa93 100644 --- a/homeassistant/components/kiwi/lock.py +++ b/homeassistant/components/kiwi/lock.py @@ -4,7 +4,7 @@ import logging from kiwiki import KiwiClient, KiwiException import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( ATTR_ID, ATTR_LATITUDE, @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([KiwiLock(lock, kiwi) for lock in available_locks], True) -class KiwiLock(LockDevice): +class KiwiLock(LockEntity): """Representation of a Kiwi lock.""" def __init__(self, kiwi_lock, client): diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 1b79108846f..fb10580a1cc 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -25,6 +25,8 @@ from homeassistant.helpers.entity_component import EntityComponent # mypy: allow-untyped-defs, no-check-untyped-defs +_LOGGER = logging.getLogger(__name__) + ATTR_CHANGED_BY = "changed_by" DOMAIN = "lock" @@ -75,7 +77,7 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class LockDevice(Entity): +class LockEntity(Entity): """Representation of a lock.""" @property @@ -134,3 +136,14 @@ class LockDevice(Entity): if locked is None: return None return STATE_LOCKED if locked else STATE_UNLOCKED + + +class LockDevice(LockEntity): + """Representation of a lock (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "LockDevice is deprecated, modify %s to extend LockEntity", cls.__name__, + ) diff --git a/homeassistant/components/lockitron/lock.py b/homeassistant/components/lockitron/lock.py index 7d34bb02472..e1ece3da725 100644 --- a/homeassistant/components/lockitron/lock.py +++ b/homeassistant/components/lockitron/lock.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ID, HTTP_OK import homeassistant.helpers.config_validation as cv @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Error retrieving lock status during init: %s", response.text) -class Lockitron(LockDevice): +class Lockitron(LockEntity): """Representation of a Lockitron lock.""" LOCK_STATE = "lock" diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 378f1b8fbcb..34905fe8aa4 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import lock, mqtt -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ( CONF_DEVICE, CONF_NAME, @@ -108,7 +108,7 @@ class MqttLock( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - LockDevice, + LockEntity, ): """Representation of a lock that can be toggled using MQTT.""" diff --git a/homeassistant/components/nello/lock.py b/homeassistant/components/nello/lock.py index 19f8e7aa14c..dc761d61461 100644 --- a/homeassistant/components/nello/lock.py +++ b/homeassistant/components/nello/lock.py @@ -5,7 +5,7 @@ import logging from pynello.private import Nello import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv @@ -27,7 +27,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([NelloLock(lock) for lock in nello.locations], True) -class NelloLock(LockDevice): +class NelloLock(LockEntity): """Representation of a Nello lock.""" def __init__(self, nello_lock): diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 943dbc02fbf..3d382496b28 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -6,7 +6,7 @@ from pynuki import NukiBridge from requests.exceptions import RequestException import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, SUPPORT_OPEN, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, SUPPORT_OPEN, LockEntity from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.service import extract_entity_ids @@ -71,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class NukiLock(LockDevice): +class NukiLock(LockEntity): """Representation of a Nuki lock.""" def __init__(self, nuki_lock): diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index fa12ff7a1b2..a2d205de240 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -4,7 +4,7 @@ from typing import Callable import pysesame2 import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( ATTR_BATTERY_LEVEL, CONF_API_KEY, @@ -32,7 +32,7 @@ def setup_platform( ) -class SesameDevice(LockDevice): +class SesameDevice(LockEntity): """Representation of a Sesame device.""" def __init__(self, sesame: object) -> None: diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index fc98d67ccbf..78866ce9004 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -5,7 +5,7 @@ from simplipy.errors import SimplipyError from simplipy.lock import LockStates from simplipy.websocket import EVENT_LOCK_LOCKED, EVENT_LOCK_UNLOCKED -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.core import callback from . import SimpliSafeEntity @@ -30,7 +30,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class SimpliSafeLock(SimpliSafeEntity, LockDevice): +class SimpliSafeLock(SimpliSafeEntity, LockEntity): """Define a SimpliSafe lock.""" def __init__(self, simplisafe, system, lock): diff --git a/homeassistant/components/smartthings/lock.py b/homeassistant/components/smartthings/lock.py index d249cc5ac94..d6b615b47a7 100644 --- a/homeassistant/components/smartthings/lock.py +++ b/homeassistant/components/smartthings/lock.py @@ -3,7 +3,7 @@ from typing import Optional, Sequence from pysmartthings import Attribute, Capability -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -38,7 +38,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: return None -class SmartThingsLock(SmartThingsEntity, LockDevice): +class SmartThingsLock(SmartThingsEntity, LockEntity): """Define a SmartThings lock.""" async def async_lock(self, **kwargs): diff --git a/homeassistant/components/starline/lock.py b/homeassistant/components/starline/lock.py index 804e8c8df2d..56cd8686186 100644 --- a/homeassistant/components/starline/lock.py +++ b/homeassistant/components/starline/lock.py @@ -1,5 +1,5 @@ """Support for StarLine lock.""" -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from .account import StarlineAccount, StarlineDevice from .const import DOMAIN @@ -19,7 +19,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class StarlineLock(StarlineEntity, LockDevice): +class StarlineLock(StarlineEntity, LockEntity): """Representation of a StarLine lock.""" def __init__(self, account: StarlineAccount, device: StarlineDevice): diff --git a/homeassistant/components/tahoma/lock.py b/homeassistant/components/tahoma/lock.py index 0b02975fc7e..93d82bffc99 100644 --- a/homeassistant/components/tahoma/lock.py +++ b/homeassistant/components/tahoma/lock.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_LOCKED, STATE_UNLOCKED from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -24,7 +24,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaLock(TahomaDevice, LockDevice): +class TahomaLock(TahomaDevice, LockEntity): """Representation a Tahoma lock.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index a5caac00123..7a50e34f8cb 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.lock import PLATFORM_SCHEMA, LockDevice +from homeassistant.components.lock import PLATFORM_SCHEMA, LockEntity from homeassistant.const import ( CONF_NAME, CONF_OPTIMISTIC, @@ -72,7 +72,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N ) -class TemplateLock(LockDevice): +class TemplateLock(LockEntity): """Representation of a template lock.""" def __init__( diff --git a/homeassistant/components/tesla/lock.py b/homeassistant/components/tesla/lock.py index 7dffff5a5e0..91833d777fd 100644 --- a/homeassistant/components/tesla/lock.py +++ b/homeassistant/components/tesla/lock.py @@ -1,7 +1,7 @@ """Support for Tesla door locks.""" import logging -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -22,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class TeslaLock(TeslaDevice, LockDevice): +class TeslaLock(TeslaDevice, LockEntity): """Representation of a Tesla door lock.""" def __init__(self, tesla_device, controller, config_entry): diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index da3c432a6af..f85beb5ba69 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -5,7 +5,7 @@ from typing import Callable, List from homeassistant.components.lock import ( DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT, - LockDevice, + LockEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED @@ -36,7 +36,7 @@ async def async_setup_entry( ) -class VeraLock(VeraDevice, LockDevice): +class VeraLock(VeraDevice, LockEntity): """Representation of a Vera lock.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/verisure/lock.py b/homeassistant/components/verisure/lock.py index 5b5d50347ac..96e40c5c36f 100644 --- a/homeassistant/components/verisure/lock.py +++ b/homeassistant/components/verisure/lock.py @@ -2,7 +2,7 @@ import logging from time import monotonic, sleep -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ATTR_CODE, STATE_LOCKED, STATE_UNLOCKED from . import CONF_CODE_DIGITS, CONF_DEFAULT_LOCK_CODE, CONF_LOCKS, HUB as hub @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(locks) -class VerisureDoorlock(LockDevice): +class VerisureDoorlock(LockEntity): """Representation of a Verisure doorlock.""" def __init__(self, device_label): diff --git a/homeassistant/components/volvooncall/lock.py b/homeassistant/components/volvooncall/lock.py index 2319674cd43..efb8423777b 100644 --- a/homeassistant/components/volvooncall/lock.py +++ b/homeassistant/components/volvooncall/lock.py @@ -1,7 +1,7 @@ """Support for Volvo On Call locks.""" import logging -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from . import DATA_KEY, VolvoEntity @@ -16,7 +16,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([VolvoLock(hass.data[DATA_KEY], *discovery_info)]) -class VolvoLock(VolvoEntity, LockDevice): +class VolvoLock(VolvoEntity, LockEntity): """Represents a car lock.""" @property diff --git a/homeassistant/components/wink/lock.py b/homeassistant/components/wink/lock.py index 57cf9d304ec..221d6d8165e 100644 --- a/homeassistant/components/wink/lock.py +++ b/homeassistant/components/wink/lock.py @@ -4,7 +4,7 @@ import logging import pywink import voluptuous as vol -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, @@ -133,7 +133,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class WinkLockDevice(WinkDevice, LockDevice): +class WinkLockDevice(WinkDevice, LockEntity): """Representation of a Wink lock.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/xiaomi_aqara/lock.py b/homeassistant/components/xiaomi_aqara/lock.py index ed71e05bf5f..c3835f83391 100644 --- a/homeassistant/components/xiaomi_aqara/lock.py +++ b/homeassistant/components/xiaomi_aqara/lock.py @@ -1,7 +1,7 @@ """Support for Xiaomi Aqara locks.""" import logging -from homeassistant.components.lock import LockDevice +from homeassistant.components.lock import LockEntity from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED from homeassistant.core import callback from homeassistant.helpers.event import async_call_later @@ -32,7 +32,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class XiaomiAqaraLock(LockDevice, XiaomiDevice): +class XiaomiAqaraLock(LockEntity, XiaomiDevice): """Representation of a XiaomiAqaraLock.""" def __init__(self, device, name, xiaomi_hub): diff --git a/homeassistant/components/zha/lock.py b/homeassistant/components/zha/lock.py index ba802120044..d70c1e2e7f3 100644 --- a/homeassistant/components/zha/lock.py +++ b/homeassistant/components/zha/lock.py @@ -8,7 +8,7 @@ from homeassistant.components.lock import ( DOMAIN, STATE_LOCKED, STATE_UNLOCKED, - LockDevice, + LockEntity, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @STRICT_MATCH(channel_names=CHANNEL_DOORLOCK) -class ZhaDoorLock(ZhaEntity, LockDevice): +class ZhaDoorLock(ZhaEntity, LockEntity): """Representation of a ZHA lock.""" def __init__(self, unique_id, zha_device, channels, **kwargs): diff --git a/homeassistant/components/zwave/lock.py b/homeassistant/components/zwave/lock.py index 0bbcf9815c6..924dfdfa4ad 100644 --- a/homeassistant/components/zwave/lock.py +++ b/homeassistant/components/zwave/lock.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.lock import DOMAIN, LockDevice +from homeassistant.components.lock import DOMAIN, LockEntity from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -239,7 +239,7 @@ def get_device(node, values, **kwargs): return ZwaveLock(values) -class ZwaveLock(ZWaveDeviceEntity, LockDevice): +class ZwaveLock(ZWaveDeviceEntity, LockEntity): """Representation of a Z-Wave Lock.""" def __init__(self, values): diff --git a/tests/components/lock/test_init.py b/tests/components/lock/test_init.py new file mode 100644 index 00000000000..a788b9fa917 --- /dev/null +++ b/tests/components/lock/test_init.py @@ -0,0 +1,12 @@ +"""The tests for Lock.""" +from homeassistant.components import lock + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomLock(lock.LockDevice): + pass + + CustomLock() + assert "LockDevice is deprecated, modify CustomLock" in caplog.text diff --git a/tests/testing_config/custom_components/test/lock.py b/tests/testing_config/custom_components/test/lock.py index 24b04903541..4894f00fd75 100644 --- a/tests/testing_config/custom_components/test/lock.py +++ b/tests/testing_config/custom_components/test/lock.py @@ -3,7 +3,7 @@ Provide a mock lock platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.lock import SUPPORT_OPEN, LockDevice +from homeassistant.components.lock import SUPPORT_OPEN, LockEntity from tests.common import MockEntity @@ -41,7 +41,7 @@ async def async_setup_platform( async_add_entities_callback(list(ENTITIES.values())) -class MockLock(MockEntity, LockDevice): +class MockLock(MockEntity, LockEntity): """Mock Lock class.""" @property From 29bc93ea988e2a76afb0a06fa5235dc182d2ed41 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 25 Apr 2020 18:04:03 +0200 Subject: [PATCH 056/511] Rename ClimateDevice to ClimateEntity (#34591) --- .../components/ambiclimate/climate.py | 4 ++-- homeassistant/components/climate/__init__.py | 22 ++++++++++++++----- .../components/coolmaster/climate.py | 4 ++-- homeassistant/components/daikin/climate.py | 4 ++-- homeassistant/components/deconz/climate.py | 4 ++-- homeassistant/components/demo/climate.py | 4 ++-- homeassistant/components/dyson/climate.py | 4 ++-- homeassistant/components/ecobee/climate.py | 4 ++-- homeassistant/components/elkm1/climate.py | 4 ++-- homeassistant/components/ephember/climate.py | 4 ++-- .../components/eq3btsmart/climate.py | 4 ++-- homeassistant/components/esphome/climate.py | 6 ++--- homeassistant/components/evohome/climate.py | 8 +++---- homeassistant/components/fibaro/climate.py | 4 ++-- homeassistant/components/flexit/climate.py | 4 ++-- homeassistant/components/fritzbox/climate.py | 4 ++-- .../components/generic_thermostat/climate.py | 4 ++-- homeassistant/components/geniushub/climate.py | 4 ++-- homeassistant/components/heatmiser/climate.py | 4 ++-- .../components/hisense_aehw4a1/climate.py | 4 ++-- homeassistant/components/hive/climate.py | 4 ++-- .../components/homekit_controller/climate.py | 6 ++--- homeassistant/components/homematic/climate.py | 4 ++-- .../components/homematicip_cloud/climate.py | 4 ++-- homeassistant/components/honeywell/climate.py | 4 ++-- homeassistant/components/iaqualink/climate.py | 4 ++-- homeassistant/components/incomfort/climate.py | 4 ++-- .../components/intesishome/climate.py | 4 ++-- homeassistant/components/izone/climate.py | 6 ++--- homeassistant/components/knx/climate.py | 4 ++-- homeassistant/components/lcn/climate.py | 4 ++-- homeassistant/components/lightwave/climate.py | 4 ++-- homeassistant/components/maxcube/climate.py | 8 +++---- homeassistant/components/melcloud/climate.py | 4 ++-- homeassistant/components/melissa/climate.py | 4 ++-- homeassistant/components/mill/climate.py | 4 ++-- homeassistant/components/modbus/climate.py | 4 ++-- homeassistant/components/mqtt/climate.py | 4 ++-- homeassistant/components/mysensors/climate.py | 4 ++-- homeassistant/components/nest/climate.py | 4 ++-- homeassistant/components/netatmo/climate.py | 4 ++-- homeassistant/components/nexia/climate.py | 4 ++-- homeassistant/components/nuheat/climate.py | 4 ++-- homeassistant/components/oem/climate.py | 4 ++-- .../components/opentherm_gw/climate.py | 4 ++-- homeassistant/components/plugwise/climate.py | 4 ++-- homeassistant/components/proliphix/climate.py | 4 ++-- .../components/radiotherm/climate.py | 4 ++-- homeassistant/components/schluter/climate.py | 4 ++-- homeassistant/components/sensibo/climate.py | 4 ++-- .../components/smartthings/climate.py | 6 ++--- homeassistant/components/spider/climate.py | 4 ++-- .../components/stiebel_eltron/climate.py | 4 ++-- homeassistant/components/tado/climate.py | 4 ++-- homeassistant/components/tesla/climate.py | 4 ++-- homeassistant/components/tfiac/climate.py | 4 ++-- homeassistant/components/toon/climate.py | 4 ++-- homeassistant/components/touchline/climate.py | 4 ++-- homeassistant/components/tuya/climate.py | 6 ++--- homeassistant/components/velbus/climate.py | 4 ++-- homeassistant/components/venstar/climate.py | 4 ++-- homeassistant/components/vera/climate.py | 4 ++-- homeassistant/components/vicare/climate.py | 4 ++-- homeassistant/components/wink/climate.py | 6 ++--- homeassistant/components/xs1/climate.py | 4 ++-- .../components/zhong_hong/climate.py | 4 ++-- homeassistant/components/zwave/climate.py | 4 ++-- tests/components/climate/test_init.py | 21 +++++++++++++++--- 68 files changed, 177 insertions(+), 150 deletions(-) diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index a8ed166903e..cb19d1329ca 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -5,7 +5,7 @@ import logging import ambiclimate import voluptuous as vol -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, HVAC_MODE_OFF, @@ -130,7 +130,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class AmbiclimateEntity(ClimateDevice): +class AmbiclimateEntity(ClimateEntity): """Representation of a Ambiclimate Thermostat device.""" def __init__(self, heater, store): diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index f3aff44ff4d..d3241791cf2 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -101,7 +101,7 @@ SET_TEMPERATURE_SCHEMA = vol.All( async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: - """Set up climate devices.""" + """Set up climate entities.""" component = hass.data[DOMAIN] = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL ) @@ -156,8 +156,8 @@ async def async_unload_entry(hass: HomeAssistantType, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class ClimateDevice(Entity): - """Representation of a climate device.""" +class ClimateEntity(Entity): + """Representation of a climate entity.""" @property def state(self) -> str: @@ -509,7 +509,7 @@ class ClimateDevice(Entity): async def async_service_aux_heat( - entity: ClimateDevice, service: ServiceDataType + entity: ClimateEntity, service: ServiceDataType ) -> None: """Handle aux heat service.""" if service.data[ATTR_AUX_HEAT]: @@ -519,7 +519,7 @@ async def async_service_aux_heat( async def async_service_temperature_set( - entity: ClimateDevice, service: ServiceDataType + entity: ClimateEntity, service: ServiceDataType ) -> None: """Handle set temperature service.""" hass = entity.hass @@ -534,3 +534,15 @@ async def async_service_temperature_set( kwargs[value] = temp await entity.async_set_temperature(**kwargs) + + +class ClimateDevice(ClimateEntity): + """Representation of a climate entity (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "ClimateDevice is deprecated, modify %s to extend ClimateEntity", + cls.__name__, + ) diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py index a52431dd89b..6e68e858a6d 100644 --- a/homeassistant/components/coolmaster/climate.py +++ b/homeassistant/components/coolmaster/climate.py @@ -4,7 +4,7 @@ import logging from pycoolmasternet import CoolMasterNet -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -60,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices): async_add_devices(all_devices, True) -class CoolmasterClimate(ClimateDevice): +class CoolmasterClimate(ClimateEntity): """Representation of a coolmaster climate device.""" def __init__(self, device, supported_modes): diff --git a/homeassistant/components/daikin/climate.py b/homeassistant/components/daikin/climate.py index 5455bd6f670..ebf909dcbda 100644 --- a/homeassistant/components/daikin/climate.py +++ b/homeassistant/components/daikin/climate.py @@ -4,7 +4,7 @@ import logging from pydaikin import appliance import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_FAN_MODE, ATTR_HVAC_MODE, @@ -86,7 +86,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities([DaikinClimate(daikin_api)], update_before_add=True) -class DaikinClimate(ClimateDevice): +class DaikinClimate(ClimateEntity): """Representation of a Daikin HVAC.""" def __init__(self, api): diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 7b0f44807ec..424693505ca 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,7 +1,7 @@ """Support for deCONZ climate devices.""" from pydeconz.sensor import Thermostat -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_climate(gateway.api.sensors.values()) -class DeconzThermostat(DeconzDevice, ClimateDevice): +class DeconzThermostat(DeconzDevice, ClimateEntity): """Representation of a deCONZ thermostat.""" @property diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index 0edcf618ba6..9733d0f1147 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -1,7 +1,7 @@ """Demo platform that offers a fake climate device.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -97,7 +97,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoClimate(ClimateDevice): +class DemoClimate(ClimateEntity): """Representation of a demo climate device.""" def __init__( diff --git a/homeassistant/components/dyson/climate.py b/homeassistant/components/dyson/climate.py index f4e23b01622..6b2d7cbe74c 100644 --- a/homeassistant/components/dyson/climate.py +++ b/homeassistant/components/dyson/climate.py @@ -5,7 +5,7 @@ from libpurecool.const import FocusMode, HeatMode, HeatState, HeatTarget from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink from libpurecool.dyson_pure_state import DysonPureHotCoolState -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ) -class DysonPureHotCoolLinkDevice(ClimateDevice): +class DysonPureHotCoolLinkDevice(ClimateEntity): """Representation of a Dyson climate fan.""" def __init__(self, device): diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index 89f464452f8..c956308ab8e 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -4,7 +4,7 @@ from typing import Optional import voluptuous as vol -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -249,7 +249,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class Thermostat(ClimateDevice): +class Thermostat(ClimateEntity): """A thermostat class for Ecobee.""" def __init__(self, data, thermostat_index): diff --git a/homeassistant/components/elkm1/climate.py b/homeassistant/components/elkm1/climate.py index baaf3d44eb2..6d10df45adf 100644 --- a/homeassistant/components/elkm1/climate.py +++ b/homeassistant/components/elkm1/climate.py @@ -1,7 +1,7 @@ """Support for control of Elk-M1 connected thermostats.""" from elkm1_lib.const import ThermostatFan, ThermostatMode, ThermostatSetting -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -39,7 +39,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class ElkThermostat(ElkEntity, ClimateDevice): +class ElkThermostat(ElkEntity, ClimateEntity): """Representation of an Elk-M1 Thermostat.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/ephember/climate.py b/homeassistant/components/ephember/climate.py index d743f3e82ba..787677a6605 100644 --- a/homeassistant/components/ephember/climate.py +++ b/homeassistant/components/ephember/climate.py @@ -15,7 +15,7 @@ from pyephember.pyephember import ( ) import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -70,7 +70,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return -class EphEmberThermostat(ClimateDevice): +class EphEmberThermostat(ClimateEntity): """Representation of a EphEmber thermostat.""" def __init__(self, ember, zone): diff --git a/homeassistant/components/eq3btsmart/climate.py b/homeassistant/components/eq3btsmart/climate.py index d0b60c74443..402dfc684b3 100644 --- a/homeassistant/components/eq3btsmart/climate.py +++ b/homeassistant/components/eq3btsmart/climate.py @@ -6,7 +6,7 @@ from bluepy.btle import BTLEException import eq3bt as eq3 # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -76,7 +76,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class EQ3BTSmartThermostat(ClimateDevice): +class EQ3BTSmartThermostat(ClimateEntity): """Representation of an eQ-3 Bluetooth Smart thermostat.""" def __init__(self, _mac, _name): diff --git a/homeassistant/components/esphome/climate.py b/homeassistant/components/esphome/climate.py index 960366a8332..46ed214afba 100644 --- a/homeassistant/components/esphome/climate.py +++ b/homeassistant/components/esphome/climate.py @@ -11,7 +11,7 @@ from aioesphomeapi import ( ClimateSwingMode, ) -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, @@ -75,7 +75,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities, component_key="climate", info_type=ClimateInfo, - entity_type=EsphomeClimateDevice, + entity_type=EsphomeClimateEntity, state_type=ClimateState, ) @@ -129,7 +129,7 @@ def _swing_modes(): } -class EsphomeClimateDevice(EsphomeEntity, ClimateDevice): +class EsphomeClimateEntity(EsphomeEntity, ClimateEntity): """A climate implementation for ESPHome.""" @property diff --git a/homeassistant/components/evohome/climate.py b/homeassistant/components/evohome/climate.py index b7899afdd7b..c6edb4aa1dc 100644 --- a/homeassistant/components/evohome/climate.py +++ b/homeassistant/components/evohome/climate.py @@ -3,7 +3,7 @@ from datetime import datetime as dt import logging from typing import List, Optional -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -122,7 +122,7 @@ async def async_setup_platform( async_add_entities([controller] + zones, update_before_add=True) -class EvoClimateDevice(EvoDevice, ClimateDevice): +class EvoClimateEntity(EvoDevice, ClimateEntity): """Base for an evohome Climate device.""" def __init__(self, evo_broker, evo_device) -> None: @@ -142,7 +142,7 @@ class EvoClimateDevice(EvoDevice, ClimateDevice): return self._preset_modes -class EvoZone(EvoChild, EvoClimateDevice): +class EvoZone(EvoChild, EvoClimateEntity): """Base for a Honeywell TCC Zone.""" def __init__(self, evo_broker, evo_device) -> None: @@ -315,7 +315,7 @@ class EvoZone(EvoChild, EvoClimateDevice): self._device_state_attrs[attr] = getattr(self._evo_device, attr) -class EvoController(EvoClimateDevice): +class EvoController(EvoClimateEntity): """Base for a Honeywell TCC Controller/Location. The Controller (aka TCS, temperature control system) is the parent of all the child diff --git a/homeassistant/components/fibaro/climate.py b/homeassistant/components/fibaro/climate.py index 71be289e27b..191185c4a2a 100644 --- a/homeassistant/components/fibaro/climate.py +++ b/homeassistant/components/fibaro/climate.py @@ -1,7 +1,7 @@ """Support for Fibaro thermostats.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_COOL, @@ -104,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroThermostat(FibaroDevice, ClimateDevice): +class FibaroThermostat(FibaroDevice, ClimateEntity): """Representation of a Fibaro Thermostat.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/flexit/climate.py b/homeassistant/components/flexit/climate.py index 68e13abf8d1..450d09edeb8 100644 --- a/homeassistant/components/flexit/climate.py +++ b/homeassistant/components/flexit/climate.py @@ -5,7 +5,7 @@ from typing import List from pyflexit.pyflexit import pyflexit import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_COOL, SUPPORT_FAN_MODE, @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([Flexit(hub, modbus_slave, name)], True) -class Flexit(ClimateDevice): +class Flexit(ClimateEntity): """Representation of a Flexit AC unit.""" def __init__(self, hub, modbus_slave, name): diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 1c95d918ab8..4abe82776a9 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -1,7 +1,7 @@ """Support for AVM Fritz!Box smarthome thermostate devices.""" import requests -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, HVAC_MODE_HEAT, @@ -61,7 +61,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FritzboxThermostat(ClimateDevice): +class FritzboxThermostat(ClimateEntity): """The thermostat class for Fritzbox smarthome thermostates.""" def __init__(self, device, fritz): diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 9a8d6177214..396d347c3c9 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_PRESET_MODE, CURRENT_HVAC_COOL, @@ -127,7 +127,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class GenericThermostat(ClimateDevice, RestoreEntity): +class GenericThermostat(ClimateEntity, RestoreEntity): """Representation of a Generic Thermostat device.""" def __init__( diff --git a/homeassistant/components/geniushub/climate.py b/homeassistant/components/geniushub/climate.py index 2221b8706c8..70d08dc2d1f 100644 --- a/homeassistant/components/geniushub/climate.py +++ b/homeassistant/components/geniushub/climate.py @@ -1,7 +1,7 @@ """Support for Genius Hub climate devices.""" from typing import List, Optional -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -45,7 +45,7 @@ async def async_setup_platform( ) -class GeniusClimateZone(GeniusHeatingZone, ClimateDevice): +class GeniusClimateZone(GeniusHeatingZone, ClimateEntity): """Representation of a Genius Hub climate device.""" def __init__(self, broker, zone) -> None: diff --git a/homeassistant/components/heatmiser/climate.py b/homeassistant/components/heatmiser/climate.py index 553ae8f4bc3..b3f3363818c 100644 --- a/homeassistant/components/heatmiser/climate.py +++ b/homeassistant/components/heatmiser/climate.py @@ -9,7 +9,7 @@ from homeassistant.components.climate import ( HVAC_MODE_HEAT, HVAC_MODE_OFF, PLATFORM_SCHEMA, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import SUPPORT_TARGET_TEMPERATURE from homeassistant.const import ( @@ -64,7 +64,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class HeatmiserV3Thermostat(ClimateDevice): +class HeatmiserV3Thermostat(ClimateEntity): """Representation of a HeatmiserV3 thermostat.""" def __init__(self, therm, device, uh1): diff --git a/homeassistant/components/hisense_aehw4a1/climate.py b/homeassistant/components/hisense_aehw4a1/climate.py index da18419c264..23a3a0c1416 100644 --- a/homeassistant/components/hisense_aehw4a1/climate.py +++ b/homeassistant/components/hisense_aehw4a1/climate.py @@ -5,7 +5,7 @@ import logging from pyaehw4a1.aehw4a1 import AehW4a1 import pyaehw4a1.exceptions -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_HIGH, @@ -144,7 +144,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class ClimateAehW4a1(ClimateDevice): +class ClimateAehW4a1(ClimateEntity): """Representation of a Hisense AEH-W4A1 module for climate device.""" def __init__(self, device): diff --git a/homeassistant/components/hive/climate.py b/homeassistant/components/hive/climate.py index 202cea7bf8e..33c8fed4eca 100644 --- a/homeassistant/components/hive/climate.py +++ b/homeassistant/components/hive/climate.py @@ -1,5 +1,5 @@ """Support for the Hive climate devices.""" -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class HiveClimateEntity(HiveEntity, ClimateDevice): +class HiveClimateEntity(HiveEntity, ClimateEntity): """Hive Climate Device.""" def __init__(self, hive_session, hive_device): diff --git a/homeassistant/components/homekit_controller/climate.py b/homeassistant/components/homekit_controller/climate.py index 2262fa54770..f06063c5fd2 100644 --- a/homeassistant/components/homekit_controller/climate.py +++ b/homeassistant/components/homekit_controller/climate.py @@ -11,7 +11,7 @@ from aiohomekit.utils import clamp_enum_to_char from homeassistant.components.climate import ( DEFAULT_MAX_HUMIDITY, DEFAULT_MIN_HUMIDITY, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, @@ -59,13 +59,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if service["stype"] != "thermostat": return False info = {"aid": aid, "iid": service["iid"]} - async_add_entities([HomeKitClimateDevice(conn, info)], True) + async_add_entities([HomeKitClimateEntity(conn, info)], True) return True conn.add_listener(async_add_service) -class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): +class HomeKitClimateEntity(HomeKitEntity, ClimateEntity): """Representation of a Homekit climate device.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/climate.py b/homeassistant/components/homematic/climate.py index b4ab277a75b..243e1782a37 100644 --- a/homeassistant/components/homematic/climate.py +++ b/homeassistant/components/homematic/climate.py @@ -1,7 +1,7 @@ """Support for Homematic thermostats.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMThermostat(HMDevice, ClimateDevice): +class HMThermostat(HMDevice, ClimateEntity): """Representation of a Homematic thermostat.""" @property diff --git a/homeassistant/components/homematicip_cloud/climate.py b/homeassistant/components/homematicip_cloud/climate.py index c5fb978e690..3d01f5d69fd 100644 --- a/homeassistant/components/homematicip_cloud/climate.py +++ b/homeassistant/components/homematicip_cloud/climate.py @@ -8,7 +8,7 @@ from homematicip.base.enums import AbsenceType from homematicip.device import Switch from homematicip.functionalHomes import IndoorClimateHome -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -57,7 +57,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice): +class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateEntity): """Representation of a HomematicIP heating group. Heat mode is supported for all heating devices incl. their defined profiles. diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index ece8257a713..5969dcdcc27 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -7,7 +7,7 @@ import requests import somecomfort import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -145,7 +145,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class HoneywellUSThermostat(ClimateDevice): +class HoneywellUSThermostat(ClimateEntity): """Representation of a Honeywell US Thermostat.""" def __init__( diff --git a/homeassistant/components/iaqualink/climate.py b/homeassistant/components/iaqualink/climate.py index 36f3303774a..2c26b2bc363 100644 --- a/homeassistant/components/iaqualink/climate.py +++ b/homeassistant/components/iaqualink/climate.py @@ -10,7 +10,7 @@ from iaqualink.const import ( AQUALINK_TEMP_FAHRENHEIT_LOW, ) -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( DOMAIN, HVAC_MODE_HEAT, @@ -39,7 +39,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkThermostat(AqualinkEntity, ClimateDevice): +class HassAqualinkThermostat(AqualinkEntity, ClimateEntity): """Representation of a thermostat.""" @property diff --git a/homeassistant/components/incomfort/climate.py b/homeassistant/components/incomfort/climate.py index 464ff989941..274308efe06 100644 --- a/homeassistant/components/incomfort/climate.py +++ b/homeassistant/components/incomfort/climate.py @@ -1,7 +1,7 @@ """Support for an Intergas boiler via an InComfort/InTouch Lan2RF gateway.""" from typing import Any, Dict, List, Optional -from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateDevice +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE, @@ -24,7 +24,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class InComfortClimate(IncomfortChild, ClimateDevice): +class InComfortClimate(IncomfortChild, ClimateEntity): """Representation of an InComfort/InTouch climate device.""" def __init__(self, client, heater, room) -> None: diff --git a/homeassistant/components/intesishome/climate.py b/homeassistant/components/intesishome/climate.py index a3a06a52c9c..ecd00bde986 100644 --- a/homeassistant/components/intesishome/climate.py +++ b/homeassistant/components/intesishome/climate.py @@ -5,7 +5,7 @@ from random import randrange from pyintesishome import IHAuthenticationError, IHConnectionError, IntesisHome import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, HVAC_MODE_COOL, @@ -129,7 +129,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= await controller.stop() -class IntesisAC(ClimateDevice): +class IntesisAC(ClimateEntity): """Represents an Intesishome air conditioning device.""" def __init__(self, ih_device_id, ih_device, controller): diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index b17313925a8..69c005b345b 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -4,7 +4,7 @@ from typing import List, Optional from pizone import Controller, Zone -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_HIGH, @@ -100,7 +100,7 @@ def _return_on_connection_error(ret=None): return wrap -class ControllerDevice(ClimateDevice): +class ControllerDevice(ClimateEntity): """Representation of iZone Controller.""" def __init__(self, controller: Controller) -> None: @@ -399,7 +399,7 @@ class ControllerDevice(ClimateDevice): await self.wrap_and_catch(self._controller.set_on(True)) -class ZoneDevice(ClimateDevice): +class ZoneDevice(ClimateEntity): """Representation of iZone Zone.""" def __init__(self, controller: ControllerDevice, zone: Zone) -> None: diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index ab590540543..a3d5ac046a4 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -5,7 +5,7 @@ import voluptuous as vol from xknx.devices import Climate as XknxClimate, ClimateMode as XknxClimateMode from xknx.knx import HVACOperationMode -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_COOL, @@ -192,7 +192,7 @@ def async_add_entities_config(hass, config, async_add_entities): async_add_entities([KNXClimate(climate)]) -class KNXClimate(ClimateDevice): +class KNXClimate(ClimateEntity): """Representation of a KNX climate device.""" def __init__(self, device): diff --git a/homeassistant/components/lcn/climate.py b/homeassistant/components/lcn/climate.py index 12fff2f479b..9634dcf8fb3 100644 --- a/homeassistant/components/lcn/climate.py +++ b/homeassistant/components/lcn/climate.py @@ -2,7 +2,7 @@ import pypck -from homeassistant.components.climate import ClimateDevice, const +from homeassistant.components.climate import ClimateEntity, const from homeassistant.const import ATTR_TEMPERATURE, CONF_ADDRESS, CONF_UNIT_OF_MEASUREMENT from . import LcnDevice @@ -38,7 +38,7 @@ async def async_setup_platform( async_add_entities(devices) -class LcnClimate(LcnDevice, ClimateDevice): +class LcnClimate(LcnDevice, ClimateEntity): """Representation of a LCN climate device.""" def __init__(self, config, address_connection): diff --git a/homeassistant/components/lightwave/climate.py b/homeassistant/components/lightwave/climate.py index 8e842624b16..0518e91dda9 100644 --- a/homeassistant/components/lightwave/climate.py +++ b/homeassistant/components/lightwave/climate.py @@ -5,7 +5,7 @@ from homeassistant.components.climate import ( HVAC_MODE_HEAT, HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import CURRENT_HVAC_HEAT, CURRENT_HVAC_OFF from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS @@ -29,7 +29,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class LightwaveTrv(ClimateDevice): +class LightwaveTrv(ClimateEntity): """Representation of a LightWaveRF TRV.""" def __init__(self, name, device_id, lwlink, serial): diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 19bbf8bf000..ccfd44af4f7 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -9,7 +9,7 @@ from maxcube.device import ( MAX_DEVICE_MODE_VACATION, ) -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -72,11 +72,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class MaxCubeClimate(ClimateDevice): - """MAX! Cube ClimateDevice.""" +class MaxCubeClimate(ClimateEntity): + """MAX! Cube ClimateEntity.""" def __init__(self, handler, name, rf_address): - """Initialize MAX! Cube ClimateDevice.""" + """Initialize MAX! Cube ClimateEntity.""" self._name = name self._rf_address = rf_address self._cubehandle = handler diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index e2d1fdd984d..bc2ac7f1026 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -13,7 +13,7 @@ from pymelcloud.atw_device import ( ) import voluptuous as vol -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, @@ -100,7 +100,7 @@ async def async_setup_entry( ) -class MelCloudClimate(ClimateDevice): +class MelCloudClimate(ClimateEntity): """Base climate device.""" def __init__(self, device: MelCloudDevice): diff --git a/homeassistant/components/melissa/climate.py b/homeassistant/components/melissa/climate.py index 4b033811f43..abbc15c936f 100644 --- a/homeassistant/components/melissa/climate.py +++ b/homeassistant/components/melissa/climate.py @@ -1,7 +1,7 @@ """Support for Melissa Climate A/C.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_HIGH, @@ -49,7 +49,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_devices) -class MelissaClimate(ClimateDevice): +class MelissaClimate(ClimateEntity): """Representation of a Melissa Climate device.""" def __init__(self, api, serial_number, init_data): diff --git a/homeassistant/components/mill/climate.py b/homeassistant/components/mill/climate.py index d904538451c..814694b0f77 100644 --- a/homeassistant/components/mill/climate.py +++ b/homeassistant/components/mill/climate.py @@ -4,7 +4,7 @@ import logging from mill import Mill import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -85,7 +85,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MillHeater(ClimateDevice): +class MillHeater(ClimateEntity): """Representation of a Mill Thermostat device.""" def __init__(self, heater, mill_data_connection): diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 5cfd9c36967..5498ed61738 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -7,7 +7,7 @@ from pymodbus.exceptions import ConnectionException, ModbusException from pymodbus.pdu import ExceptionResponse import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, SUPPORT_TARGET_TEMPERATURE, @@ -115,7 +115,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ModbusThermostat(ClimateDevice): +class ModbusThermostat(ClimateEntity): """Representation of a Modbus Thermostat.""" def __init__( diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 91e30c7b1b1..8cc6a3ffbdb 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components import climate, mqtt from homeassistant.components.climate import ( PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, @@ -273,7 +273,7 @@ class MqttClimate( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - ClimateDevice, + ClimateEntity, ): """Representation of an MQTT climate device.""" diff --git a/homeassistant/components/mysensors/climate.py b/homeassistant/components/mysensors/climate.py index b00a0d0d9d5..c318ccf7ec6 100644 --- a/homeassistant/components/mysensors/climate.py +++ b/homeassistant/components/mysensors/climate.py @@ -1,6 +1,6 @@ """MySensors platform that offers a Climate (MySensors-HVAC) component.""" from homeassistant.components import mysensors -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -43,7 +43,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): +class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateEntity): """Representation of a MySensors HVAC.""" @property diff --git a/homeassistant/components/nest/climate.py b/homeassistant/components/nest/climate.py index 92442479091..9a84010a3bc 100644 --- a/homeassistant/components/nest/climate.py +++ b/homeassistant/components/nest/climate.py @@ -4,7 +4,7 @@ import logging from nest.nest import APIError import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -88,7 +88,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(all_devices, True) -class NestThermostat(ClimateDevice): +class NestThermostat(ClimateEntity): """Representation of a Nest thermostat.""" def __init__(self, structure, device, temp_unit): diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index eb4ec52c0f9..8de2694095e 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -7,7 +7,7 @@ import pyatmo import requests import voluptuous as vol -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -156,7 +156,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return -class NetatmoThermostat(ClimateDevice): +class NetatmoThermostat(ClimateEntity): """Representation a Netatmo thermostat.""" def __init__(self, data, room_id): diff --git a/homeassistant/components/nexia/climate.py b/homeassistant/components/nexia/climate.py index fafa267c914..b22b185a44c 100644 --- a/homeassistant/components/nexia/climate.py +++ b/homeassistant/components/nexia/climate.py @@ -13,7 +13,7 @@ from nexia.const import ( ) import voluptuous as vol -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HUMIDITY, ATTR_MAX_HUMIDITY, @@ -133,7 +133,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class NexiaZone(NexiaThermostatZoneEntity, ClimateDevice): +class NexiaZone(NexiaThermostatZoneEntity, ClimateEntity): """Provides Nexia Climate support.""" def __init__(self, coordinator, zone): diff --git a/homeassistant/components/nuheat/climate.py b/homeassistant/components/nuheat/climate.py index c1d591c03eb..417beecee9a 100644 --- a/homeassistant/components/nuheat/climate.py +++ b/homeassistant/components/nuheat/climate.py @@ -11,7 +11,7 @@ from nuheat.util import ( nuheat_to_fahrenheit, ) -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, CURRENT_HVAC_HEAT, @@ -77,7 +77,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities([entity], True) -class NuHeatThermostat(ClimateDevice): +class NuHeatThermostat(ClimateEntity): """Representation of a NuHeat Thermostat.""" def __init__(self, thermostat, temperature_unit): diff --git a/homeassistant/components/oem/climate.py b/homeassistant/components/oem/climate.py index b2ecf5a7997..734fe12d0f5 100644 --- a/homeassistant/components/oem/climate.py +++ b/homeassistant/components/oem/climate.py @@ -5,7 +5,7 @@ from oemthermostat import Thermostat import requests import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities((ThermostatDevice(therm, name),), True) -class ThermostatDevice(ClimateDevice): +class ThermostatDevice(ClimateEntity): """Interface class for the oemthermostat module.""" def __init__(self, thermostat, name): diff --git a/homeassistant/components/opentherm_gw/climate.py b/homeassistant/components/opentherm_gw/climate.py index a7e7eedef34..64625541352 100644 --- a/homeassistant/components/opentherm_gw/climate.py +++ b/homeassistant/components/opentherm_gw/climate.py @@ -3,7 +3,7 @@ import logging from pyotgw import vars as gw_vars -from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, @@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(ents) -class OpenThermClimate(ClimateDevice): +class OpenThermClimate(ClimateEntity): """Representation of a climate device.""" def __init__(self, gw_dev, options): diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index aef1dd78197..8e2e525217a 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -5,7 +5,7 @@ import logging import haanna import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, @@ -88,7 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class ThermostatDevice(ClimateDevice): +class ThermostatDevice(ClimateEntity): """Representation of the Plugwise thermostat.""" def __init__(self, api, name, min_temp, max_temp): diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index 44e31e24fb0..fc44dfba75b 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -2,7 +2,7 @@ import proliphix import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_HEAT, @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ProliphixThermostat(pdp)], True) -class ProliphixThermostat(ClimateDevice): +class ProliphixThermostat(ClimateEntity): """Representation a Proliphix thermostat.""" def __init__(self, pdp): diff --git a/homeassistant/components/radiotherm/climate.py b/homeassistant/components/radiotherm/climate.py index acbd6fe7e5e..c5e7f2f956e 100644 --- a/homeassistant/components/radiotherm/climate.py +++ b/homeassistant/components/radiotherm/climate.py @@ -4,7 +4,7 @@ import logging import radiotherm import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, @@ -125,7 +125,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(tstats, True) -class RadioThermostat(ClimateDevice): +class RadioThermostat(ClimateEntity): """Representation of a Radio Thermostat.""" def __init__(self, device, hold_temp): diff --git a/homeassistant/components/schluter/climate.py b/homeassistant/components/schluter/climate.py index d91020c7c2b..88630cb99db 100644 --- a/homeassistant/components/schluter/climate.py +++ b/homeassistant/components/schluter/climate.py @@ -8,7 +8,7 @@ from homeassistant.components.climate import ( PLATFORM_SCHEMA, SCAN_INTERVAL, TEMP_CELSIUS, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, @@ -63,7 +63,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SchluterThermostat(ClimateDevice): +class SchluterThermostat(ClimateEntity): """Representation of a Schluter thermostat.""" def __init__(self, coordinator, serial_number, api, session_id): diff --git a/homeassistant/components/sensibo/climate.py b/homeassistant/components/sensibo/climate.py index 08e2212e2a2..93d992d1d89 100644 --- a/homeassistant/components/sensibo/climate.py +++ b/homeassistant/components/sensibo/climate.py @@ -8,7 +8,7 @@ import async_timeout import pysensibo import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_DRY, @@ -135,7 +135,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SensiboClimate(ClimateDevice): +class SensiboClimate(ClimateEntity): """Representation of a Sensibo device.""" def __init__(self, client, data, units): diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index 83a1af981db..e9c0e749ca8 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -5,7 +5,7 @@ from typing import Iterable, Optional, Sequence from pysmartthings import Attribute, Capability -from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateDevice +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, @@ -144,7 +144,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: return None -class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): +class SmartThingsThermostat(SmartThingsEntity, ClimateEntity): """Define a SmartThings climate entities.""" def __init__(self, device): @@ -323,7 +323,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateDevice): return UNIT_MAP.get(self._device.status.attributes[Attribute.temperature].unit) -class SmartThingsAirConditioner(SmartThingsEntity, ClimateDevice): +class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity): """Define a SmartThings Air Conditioner.""" def __init__(self, device): diff --git a/homeassistant/components/spider/climate.py b/homeassistant/components/spider/climate.py index 846ed6a1ac6..78c77f3679a 100644 --- a/homeassistant/components/spider/climate.py +++ b/homeassistant/components/spider/climate.py @@ -2,7 +2,7 @@ import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_COOL, HVAC_MODE_HEAT, @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class SpiderThermostat(ClimateDevice): +class SpiderThermostat(ClimateEntity): """Representation of a thermostat.""" def __init__(self, api, thermostat): diff --git a/homeassistant/components/stiebel_eltron/climate.py b/homeassistant/components/stiebel_eltron/climate.py index ce16b10f548..d8c32575b17 100644 --- a/homeassistant/components/stiebel_eltron/climate.py +++ b/homeassistant/components/stiebel_eltron/climate.py @@ -1,7 +1,7 @@ """Support for stiebel_eltron climate platform.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([StiebelEltron(name, ste_data)], True) -class StiebelEltron(ClimateDevice): +class StiebelEltron(ClimateEntity): """Representation of a STIEBEL ELTRON heat pump.""" def __init__(self, name, ste_data): diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 5e1bfa94dec..cdcc32f7ac2 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -1,7 +1,7 @@ """Support for Tado thermostats.""" import logging -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_OFF, FAN_AUTO, @@ -163,7 +163,7 @@ def create_climate_entity(tado, name: str, zone_id: int, zone: dict): return entity -class TadoClimate(TadoZoneEntity, ClimateDevice): +class TadoClimate(TadoZoneEntity, ClimateEntity): """Representation of a Tado climate entity.""" def __init__( diff --git a/homeassistant/components/tesla/climate.py b/homeassistant/components/tesla/climate.py index 5ba6f182c0a..31269155d91 100644 --- a/homeassistant/components/tesla/climate.py +++ b/homeassistant/components/tesla/climate.py @@ -4,7 +4,7 @@ from typing import List, Optional from teslajsonpy.exceptions import UnknownPresetMode -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, @@ -37,7 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TeslaThermostat(TeslaDevice, ClimateDevice): +class TeslaThermostat(TeslaDevice, ClimateEntity): """Representation of a Tesla climate.""" def __init__(self, tesla_device, controller, config_entry): diff --git a/homeassistant/components/tfiac/climate.py b/homeassistant/components/tfiac/climate.py index 6d23018e897..1e9bb86d8fd 100644 --- a/homeassistant/components/tfiac/climate.py +++ b/homeassistant/components/tfiac/climate.py @@ -6,7 +6,7 @@ import logging from pytfiac import Tfiac import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( FAN_AUTO, FAN_HIGH, @@ -73,7 +73,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N async_add_devices([TfiacClimate(hass, tfiac_client)]) -class TfiacClimate(ClimateDevice): +class TfiacClimate(ClimateEntity): """TFIAC class.""" def __init__(self, hass, client): diff --git a/homeassistant/components/toon/climate.py b/homeassistant/components/toon/climate.py index fac9cf4ffc2..f3c3d9a69bf 100644 --- a/homeassistant/components/toon/climate.py +++ b/homeassistant/components/toon/climate.py @@ -3,7 +3,7 @@ import logging from typing import Any, Dict, List, Optional -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -43,7 +43,7 @@ async def async_setup_entry( async_add_entities([ToonThermostatDevice(toon_client, toon_data)], True) -class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateDevice): +class ToonThermostatDevice(ToonDisplayDeviceEntity, ClimateEntity): """Representation of a Toon climate device.""" def __init__(self, toon_client, toon_data: ToonData) -> None: diff --git a/homeassistant/components/touchline/climate.py b/homeassistant/components/touchline/climate.py index 984e454ae02..218a6a420ff 100644 --- a/homeassistant/components/touchline/climate.py +++ b/homeassistant/components/touchline/climate.py @@ -5,7 +5,7 @@ from typing import List from pytouchline import PyTouchline import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE, @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class Touchline(ClimateDevice): +class Touchline(ClimateEntity): """Representation of a Touchline device.""" def __init__(self, touchline_thermostat): diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index fe1fcc802ff..d2f7dba4c33 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -1,5 +1,5 @@ """Support for the Tuya climate devices.""" -from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice +from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateEntity from homeassistant.components.climate.const import ( FAN_HIGH, FAN_LOW, @@ -48,11 +48,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device = tuya.get_device_by_id(dev_id) if device is None: continue - devices.append(TuyaClimateDevice(device)) + devices.append(TuyaClimateEntity(device)) add_entities(devices) -class TuyaClimateDevice(TuyaDevice, ClimateDevice): +class TuyaClimateEntity(TuyaDevice, ClimateEntity): """Tuya climate devices,include air conditioner,heater.""" def __init__(self, tuya): diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 8810f945ba9..6ef91d65c91 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -3,7 +3,7 @@ import logging from velbus.util import VelbusException -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE, @@ -27,7 +27,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusClimate(VelbusEntity, ClimateDevice): +class VelbusClimate(VelbusEntity, ClimateEntity): """Representation of a Velbus thermostat.""" @property diff --git a/homeassistant/components/venstar/climate.py b/homeassistant/components/venstar/climate.py index 7de6427b5d8..261339f70dd 100644 --- a/homeassistant/components/venstar/climate.py +++ b/homeassistant/components/venstar/climate.py @@ -4,7 +4,7 @@ import logging from venstarcolortouch import VenstarColorTouch import voluptuous as vol -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, ATTR_TARGET_TEMP_HIGH, @@ -96,7 +96,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([VenstarThermostat(client, humidifier)], True) -class VenstarThermostat(ClimateDevice): +class VenstarThermostat(ClimateEntity): """Representation of a Venstar thermostat.""" def __init__(self, client, humidifier): diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 520c3b516df..9b8601e45d1 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -5,7 +5,7 @@ from typing import Callable, List from homeassistant.components.climate import ( DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT, - ClimateDevice, + ClimateEntity, ) from homeassistant.components.climate.const import ( FAN_AUTO, @@ -49,7 +49,7 @@ async def async_setup_entry( ) -class VeraThermostat(VeraDevice, ClimateDevice): +class VeraThermostat(VeraDevice, ClimateEntity): """Representation of a Vera Thermostat.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 1b101cc7612..ce88ea8e3e7 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -3,7 +3,7 @@ import logging import requests -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -97,7 +97,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ViCareClimate(ClimateDevice): +class ViCareClimate(ClimateEntity): """Representation of the ViCare heating climate device.""" def __init__(self, name, api, heating_type): diff --git a/homeassistant/components/wink/climate.py b/homeassistant/components/wink/climate.py index 85d477313f1..28be557c65e 100644 --- a/homeassistant/components/wink/climate.py +++ b/homeassistant/components/wink/climate.py @@ -3,7 +3,7 @@ import logging import pywink -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -80,7 +80,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([WinkAC(climate, hass)]) -class WinkThermostat(WinkDevice, ClimateDevice): +class WinkThermostat(WinkDevice, ClimateEntity): """Representation of a Wink thermostat.""" @property @@ -381,7 +381,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): return return_value -class WinkAC(WinkDevice, ClimateDevice): +class WinkAC(WinkDevice, ClimateEntity): """Representation of a Wink air conditioner.""" @property diff --git a/homeassistant/components/xs1/climate.py b/homeassistant/components/xs1/climate.py index 19d5ae1e904..c57c0857817 100644 --- a/homeassistant/components/xs1/climate.py +++ b/homeassistant/components/xs1/climate.py @@ -3,7 +3,7 @@ import logging from xs1_api_client.api_constants import ActuatorType -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE, @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(thermostat_entities) -class XS1ThermostatEntity(XS1DeviceEntity, ClimateDevice): +class XS1ThermostatEntity(XS1DeviceEntity, ClimateEntity): """Representation of a XS1 thermostat.""" def __init__(self, device, sensor): diff --git a/homeassistant/components/zhong_hong/climate.py b/homeassistant/components/zhong_hong/climate.py index 62f5b9acbaf..fc5a5688027 100644 --- a/homeassistant/components/zhong_hong/climate.py +++ b/homeassistant/components/zhong_hong/climate.py @@ -5,7 +5,7 @@ import voluptuous as vol from zhong_hong_hvac.hub import ZhongHongGateway from zhong_hong_hvac.hvac import HVAC as ZhongHongHVAC -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateDevice +from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( ATTR_HVAC_MODE, HVAC_MODE_COOL, @@ -113,7 +113,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_listen) -class ZhongHongClimate(ClimateDevice): +class ZhongHongClimate(ClimateEntity): """Representation of a ZhongHong controller support HVAC.""" def __init__(self, hub, addr_out, addr_in): diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 4ee9b8b9cc9..9c9c1ed6128 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -3,7 +3,7 @@ import logging from typing import Optional, Tuple -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, @@ -149,7 +149,7 @@ def get_device(hass, values, **kwargs): return None -class ZWaveClimateBase(ZWaveDeviceEntity, ClimateDevice): +class ZWaveClimateBase(ZWaveDeviceEntity, ClimateEntity): """Representation of a Z-Wave Climate device.""" def __init__(self, values, temp_unit): diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 4345ecedcf7..b5ec45d3aa0 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -10,6 +10,7 @@ from homeassistant.components.climate import ( HVAC_MODE_OFF, SET_TEMPERATURE_SCHEMA, ClimateDevice, + ClimateEntity, ) from tests.common import async_mock_service @@ -45,7 +46,7 @@ async def test_set_temp_schema(hass, caplog): assert calls[-1].data == data -class MockClimateDevice(ClimateDevice): +class MockClimateEntity(ClimateEntity): """Mock Climate device to use in tests.""" @property @@ -67,7 +68,7 @@ class MockClimateDevice(ClimateDevice): async def test_sync_turn_on(hass): """Test if async turn_on calls sync turn_on.""" - climate = MockClimateDevice() + climate = MockClimateEntity() climate.hass = hass climate.turn_on = MagicMock() @@ -78,10 +79,24 @@ async def test_sync_turn_on(hass): async def test_sync_turn_off(hass): """Test if async turn_off calls sync turn_off.""" - climate = MockClimateDevice() + climate = MockClimateEntity() climate.hass = hass climate.turn_off = MagicMock() await climate.async_turn_off() assert climate.turn_off.called + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomClimate(ClimateDevice): + def hvac_mode(self): + pass + + def hvac_modes(self): + pass + + CustomClimate() + assert "ClimateDevice is deprecated, modify CustomClimate" in caplog.text From 6f7f5b4034bc55246a8fa170dd330b1edec9ea57 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 25 Apr 2020 18:05:28 +0200 Subject: [PATCH 057/511] Rename AlarmControlPanel to AlarmControlPanelEntity (#34590) --- .../components/abode/alarm_control_panel.py | 2 +- .../components/alarm_control_panel/__init__.py | 18 ++++++++++++++++-- .../alarmdecoder/alarm_control_panel.py | 4 ++-- .../components/arlo/alarm_control_panel.py | 4 ++-- .../components/blink/alarm_control_panel.py | 4 ++-- .../components/canary/alarm_control_panel.py | 4 ++-- .../concord232/alarm_control_panel.py | 2 +- .../components/egardia/alarm_control_panel.py | 2 +- .../components/elkm1/alarm_control_panel.py | 4 ++-- .../envisalink/alarm_control_panel.py | 4 ++-- .../homekit_controller/alarm_control_panel.py | 6 +++--- .../homematicip_cloud/alarm_control_panel.py | 6 +++--- .../components/ialarm/alarm_control_panel.py | 2 +- .../components/ifttt/alarm_control_panel.py | 4 ++-- .../components/lupusec/alarm_control_panel.py | 4 ++-- .../components/manual/alarm_control_panel.py | 2 +- .../manual_mqtt/alarm_control_panel.py | 2 +- .../components/mqtt/alarm_control_panel.py | 2 +- .../ness_alarm/alarm_control_panel.py | 2 +- .../components/nx584/alarm_control_panel.py | 2 +- .../components/point/alarm_control_panel.py | 4 ++-- .../satel_integra/alarm_control_panel.py | 2 +- .../simplisafe/alarm_control_panel.py | 4 ++-- .../components/spc/alarm_control_panel.py | 2 +- .../components/template/alarm_control_panel.py | 4 ++-- .../totalconnect/alarm_control_panel.py | 2 +- .../components/verisure/alarm_control_panel.py | 2 +- .../components/wink/alarm_control_panel.py | 2 +- .../yale_smart_alarm/alarm_control_panel.py | 4 ++-- .../alarm_control_panel/test_init.py | 13 +++++++++++++ .../test/alarm_control_panel.py | 4 ++-- 31 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 tests/components/alarm_control_panel/test_init.py diff --git a/homeassistant/components/abode/alarm_control_panel.py b/homeassistant/components/abode/alarm_control_panel.py index 40040d90d0d..c508d0f0240 100644 --- a/homeassistant/components/abode/alarm_control_panel.py +++ b/homeassistant/components/abode/alarm_control_panel.py @@ -25,7 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel): +class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity): """An alarm_control_panel implementation for Abode.""" @property diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 67b0309e513..50b8adb4c03 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -32,6 +32,8 @@ from .const import ( SUPPORT_ALARM_TRIGGER, ) +_LOGGER = logging.getLogger(__name__) + DOMAIN = "alarm_control_panel" SCAN_INTERVAL = timedelta(seconds=30) ATTR_CHANGED_BY = "changed_by" @@ -99,8 +101,8 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class AlarmControlPanel(Entity): - """An abstract class for alarm control devices.""" +class AlarmControlPanelEntity(Entity): + """An abstract class for alarm control entities.""" @property def code_format(self): @@ -179,3 +181,15 @@ class AlarmControlPanel(Entity): ATTR_CODE_ARM_REQUIRED: self.code_arm_required, } return state_attr + + +class AlarmControlPanel(AlarmControlPanelEntity): + """An abstract class for alarm control entities (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "AlarmControlPanel is deprecated, modify %s to extend AlarmControlPanelEntity", + cls.__name__, + ) diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 5625204c762..ac90ea1796f 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -74,7 +74,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class AlarmDecoderAlarmPanel(AlarmControlPanel): +class AlarmDecoderAlarmPanel(AlarmControlPanelEntity): """Representation of an AlarmDecoder-based alarm panel.""" def __init__(self, auto_bypass, code_arm_required): diff --git a/homeassistant/components/arlo/alarm_control_panel.py b/homeassistant/components/arlo/alarm_control_panel.py index 7440db0495e..47328d5cbc2 100644 --- a/homeassistant/components/arlo/alarm_control_panel.py +++ b/homeassistant/components/arlo/alarm_control_panel.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( PLATFORM_SCHEMA, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -66,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(base_stations, True) -class ArloBaseStation(AlarmControlPanel): +class ArloBaseStation(AlarmControlPanelEntity): """Representation of an Arlo Alarm Control Panel.""" def __init__(self, data, home_mode_name, away_mode_name, night_mode_name): diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index 9b23c1606d4..e6af3780aaf 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for Blink Alarm Control Panel.""" import logging -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import SUPPORT_ALARM_ARM_AWAY from homeassistant.const import ( ATTR_ATTRIBUTION, @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sync_modules, True) -class BlinkSyncModule(AlarmControlPanel): +class BlinkSyncModule(AlarmControlPanelEntity): """Representation of a Blink Alarm Control Panel.""" def __init__(self, data, name, sync): diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index 35fff8accbd..a5930e658fb 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -3,7 +3,7 @@ import logging from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class CanaryAlarm(AlarmControlPanel): +class CanaryAlarm(AlarmControlPanelEntity): """Representation of a Canary alarm control panel.""" def __init__(self, data, location_id): diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index afb7e23e8fc..94880dcccf7 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -60,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Unable to connect to Concord232: %s", str(ex)) -class Concord232Alarm(alarm.AlarmControlPanel): +class Concord232Alarm(alarm.AlarmControlPanelEntity): """Representation of the Concord232-based alarm panel.""" def __init__(self, url, name, code, mode): diff --git a/homeassistant/components/egardia/alarm_control_panel.py b/homeassistant/components/egardia/alarm_control_panel.py index 7e5f88cff3e..b133a96b820 100644 --- a/homeassistant/components/egardia/alarm_control_panel.py +++ b/homeassistant/components/egardia/alarm_control_panel.py @@ -53,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([device], True) -class EgardiaAlarm(alarm.AlarmControlPanel): +class EgardiaAlarm(alarm.AlarmControlPanelEntity): """Representation of a Egardia alarm.""" def __init__( diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index b217988f8d8..3e9ab114837 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( ATTR_CHANGED_BY, FORMAT_NUMBER, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -109,7 +109,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class ElkArea(ElkAttachedEntity, AlarmControlPanel, RestoreEntity): +class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): """Representation of an Area / Partition within the ElkM1 alarm panel.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/envisalink/alarm_control_panel.py b/homeassistant/components/envisalink/alarm_control_panel.py index 62c57daf19d..670dc78392f 100644 --- a/homeassistant/components/envisalink/alarm_control_panel.py +++ b/homeassistant/components/envisalink/alarm_control_panel.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -96,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanel): +class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity): """Representation of an Envisalink-based alarm panel.""" def __init__( diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 9e712b4127f..0b8f0b3b2f8 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -3,7 +3,7 @@ import logging from aiohomekit.model.characteristics import CharacteristicsTypes -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -51,13 +51,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if service["stype"] != "security-system": return False info = {"aid": aid, "iid": service["iid"]} - async_add_entities([HomeKitAlarmControlPanel(conn, info)], True) + async_add_entities([HomeKitAlarmControlPanelEntity(conn, info)], True) return True conn.add_listener(async_add_service) -class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel): +class HomeKitAlarmControlPanelEntity(HomeKitEntity, AlarmControlPanelEntity): """Representation of a Homekit Alarm Control Panel.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematicip_cloud/alarm_control_panel.py b/homeassistant/components/homematicip_cloud/alarm_control_panel.py index fd3958344f5..7e06cd60536 100644 --- a/homeassistant/components/homematicip_cloud/alarm_control_panel.py +++ b/homeassistant/components/homematicip_cloud/alarm_control_panel.py @@ -4,7 +4,7 @@ from typing import Any, Dict from homematicip.functionalHomes import SecurityAndAlarmHome -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -32,10 +32,10 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP alrm control panel from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - async_add_entities([HomematicipAlarmControlPanel(hap)]) + async_add_entities([HomematicipAlarmControlPanelEntity(hap)]) -class HomematicipAlarmControlPanel(AlarmControlPanel): +class HomematicipAlarmControlPanelEntity(AlarmControlPanelEntity): """Representation of an alarm control panel.""" def __init__(self, hap: HomematicipHAP) -> None: diff --git a/homeassistant/components/ialarm/alarm_control_panel.py b/homeassistant/components/ialarm/alarm_control_panel.py index 24ab2bc7a80..67c9ec891a6 100644 --- a/homeassistant/components/ialarm/alarm_control_panel.py +++ b/homeassistant/components/ialarm/alarm_control_panel.py @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ialarm], True) -class IAlarmPanel(alarm.AlarmControlPanel): +class IAlarmPanel(alarm.AlarmControlPanelEntity): """Representation of an iAlarm status.""" def __init__(self, name, code, username, password, url): diff --git a/homeassistant/components/ifttt/alarm_control_panel.py b/homeassistant/components/ifttt/alarm_control_panel.py index 2c281e58c48..783cd16fefe 100644 --- a/homeassistant/components/ifttt/alarm_control_panel.py +++ b/homeassistant/components/ifttt/alarm_control_panel.py @@ -8,7 +8,7 @@ from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, FORMAT_TEXT, PLATFORM_SCHEMA, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -108,7 +108,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class IFTTTAlarmPanel(AlarmControlPanel): +class IFTTTAlarmPanel(AlarmControlPanelEntity): """Representation of an alarm control panel controlled through IFTTT.""" def __init__( diff --git a/homeassistant/components/lupusec/alarm_control_panel.py b/homeassistant/components/lupusec/alarm_control_panel.py index c6ad817bfbf..78f476be751 100644 --- a/homeassistant/components/lupusec/alarm_control_panel.py +++ b/homeassistant/components/lupusec/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for Lupusec System alarm control panels.""" from datetime import timedelta -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(alarm_devices) -class LupusecAlarm(LupusecDevice, AlarmControlPanel): +class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity): """An alarm_control_panel implementation for Lupusec.""" @property diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 89f5e34213b..4e361b5086c 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -164,7 +164,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity): +class ManualAlarm(alarm.AlarmControlPanelEntity, RestoreEntity): """ Representation of an alarm status. diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index 00a82118ec4..548227173f4 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -184,7 +184,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ManualMQTTAlarm(alarm.AlarmControlPanel): +class ManualMQTTAlarm(alarm.AlarmControlPanelEntity): """ Representation of an alarm status. diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index ad39eecdcca..dae94b5a781 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -135,7 +135,7 @@ class MqttAlarm( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - alarm.AlarmControlPanel, + alarm.AlarmControlPanelEntity, ): """Representation of a MQTT alarm status.""" diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py index 8181e54640d..d92e944c10f 100644 --- a/homeassistant/components/ness_alarm/alarm_control_panel.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -34,7 +34,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([device]) -class NessAlarmPanel(alarm.AlarmControlPanel): +class NessAlarmPanel(alarm.AlarmControlPanelEntity): """Representation of a Ness alarm panel.""" def __init__(self, client, name): diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py index 7a064ef0d00..bc2c5034ed1 100644 --- a/homeassistant/components/nx584/alarm_control_panel.py +++ b/homeassistant/components/nx584/alarm_control_panel.py @@ -52,7 +52,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return -class NX584Alarm(alarm.AlarmControlPanel): +class NX584Alarm(alarm.AlarmControlPanelEntity): """Representation of a NX584-based alarm panel.""" def __init__(self, hass, url, name): diff --git a/homeassistant/components/point/alarm_control_panel.py b/homeassistant/components/point/alarm_control_panel.py index d84c408e43a..25e135f59cb 100644 --- a/homeassistant/components/point/alarm_control_panel.py +++ b/homeassistant/components/point/alarm_control_panel.py @@ -1,7 +1,7 @@ """Support for Minut Point.""" import logging -from homeassistant.components.alarm_control_panel import DOMAIN, AlarmControlPanel +from homeassistant.components.alarm_control_panel import DOMAIN, AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import SUPPORT_ALARM_ARM_AWAY from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, @@ -36,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MinutPointAlarmControl(AlarmControlPanel): +class MinutPointAlarmControl(AlarmControlPanelEntity): """The platform class required by Home Assistant.""" def __init__(self, point_client, home_id): diff --git a/homeassistant/components/satel_integra/alarm_control_panel.py b/homeassistant/components/satel_integra/alarm_control_panel.py index 8a240794580..6f1532e1013 100644 --- a/homeassistant/components/satel_integra/alarm_control_panel.py +++ b/homeassistant/components/satel_integra/alarm_control_panel.py @@ -52,7 +52,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class SatelIntegraAlarmPanel(alarm.AlarmControlPanel): +class SatelIntegraAlarmPanel(alarm.AlarmControlPanelEntity): """Representation of an AlarmDecoder-based alarm panel.""" def __init__(self, controller, name, arm_home_mode, partition_id): diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 867a1044856..7998de463f6 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -21,7 +21,7 @@ from simplipy.websocket import ( from homeassistant.components.alarm_control_panel import ( FORMAT_NUMBER, FORMAT_TEXT, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -72,7 +72,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanel): +class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): """Representation of a SimpliSafe alarm.""" def __init__(self, simplisafe, system): diff --git a/homeassistant/components/spc/alarm_control_panel.py b/homeassistant/components/spc/alarm_control_panel.py index 982c0fe2bab..86adc588038 100644 --- a/homeassistant/components/spc/alarm_control_panel.py +++ b/homeassistant/components/spc/alarm_control_panel.py @@ -47,7 +47,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([SpcAlarm(area=area, api=api) for area in api.areas.values()]) -class SpcAlarm(alarm.AlarmControlPanel): +class SpcAlarm(alarm.AlarmControlPanelEntity): """Representation of the SPC alarm panel.""" def __init__(self, area, api): diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index 937119ff6d4..3d6ac1dbe0e 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -7,7 +7,7 @@ from homeassistant.components.alarm_control_panel import ( ENTITY_ID_FORMAT, FORMAT_NUMBER, PLATFORM_SCHEMA, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -117,7 +117,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(alarm_control_panels) -class AlarmControlPanelTemplate(AlarmControlPanel): +class AlarmControlPanelTemplate(AlarmControlPanelEntity): """Representation of a templated Alarm Control Panel.""" def __init__( diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index 2a32ae89b4a..632673233ec 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -36,7 +36,7 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: async_add_entities(alarms, True) -class TotalConnectAlarm(alarm.AlarmControlPanel): +class TotalConnectAlarm(alarm.AlarmControlPanelEntity): """Represent an TotalConnect status.""" def __init__(self, name, location_id, client): diff --git a/homeassistant/components/verisure/alarm_control_panel.py b/homeassistant/components/verisure/alarm_control_panel.py index 78a09e439d7..1ef3eb442cd 100644 --- a/homeassistant/components/verisure/alarm_control_panel.py +++ b/homeassistant/components/verisure/alarm_control_panel.py @@ -40,7 +40,7 @@ def set_arm_state(state, code=None): hub.update_overview(no_throttle=True) -class VerisureAlarm(alarm.AlarmControlPanel): +class VerisureAlarm(alarm.AlarmControlPanelEntity): """Representation of a Verisure alarm status.""" def __init__(self): diff --git a/homeassistant/components/wink/alarm_control_panel.py b/homeassistant/components/wink/alarm_control_panel.py index 733022e91b1..89dc6a46815 100644 --- a/homeassistant/components/wink/alarm_control_panel.py +++ b/homeassistant/components/wink/alarm_control_panel.py @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([WinkCameraDevice(camera, hass)]) -class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel): +class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanelEntity): """Representation a Wink camera alarm.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py index 05823a511dd..d3504bcc6da 100644 --- a/homeassistant/components/yale_smart_alarm/alarm_control_panel.py +++ b/homeassistant/components/yale_smart_alarm/alarm_control_panel.py @@ -12,7 +12,7 @@ from yalesmartalarmclient.client import ( from homeassistant.components.alarm_control_panel import ( PLATFORM_SCHEMA, - AlarmControlPanel, + AlarmControlPanelEntity, ) from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([YaleAlarmDevice(name, client)], True) -class YaleAlarmDevice(AlarmControlPanel): +class YaleAlarmDevice(AlarmControlPanelEntity): """Represent a Yale Smart Alarm.""" def __init__(self, name, client): diff --git a/tests/components/alarm_control_panel/test_init.py b/tests/components/alarm_control_panel/test_init.py new file mode 100644 index 00000000000..257d764468a --- /dev/null +++ b/tests/components/alarm_control_panel/test_init.py @@ -0,0 +1,13 @@ +"""Tests for Alarm control panel.""" +from homeassistant.components import alarm_control_panel + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomAlarm(alarm_control_panel.AlarmControlPanel): + def supported_features(self): + pass + + CustomAlarm() + assert "AlarmControlPanel is deprecated, modify CustomAlarm" in caplog.text diff --git a/tests/testing_config/custom_components/test/alarm_control_panel.py b/tests/testing_config/custom_components/test/alarm_control_panel.py index 065ea3e3980..5edb1ca2f70 100644 --- a/tests/testing_config/custom_components/test/alarm_control_panel.py +++ b/tests/testing_config/custom_components/test/alarm_control_panel.py @@ -3,7 +3,7 @@ Provide a mock alarm_control_panel platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.alarm_control_panel import AlarmControlPanel +from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity from homeassistant.components.alarm_control_panel.const import ( SUPPORT_ALARM_ARM_AWAY, SUPPORT_ALARM_ARM_HOME, @@ -52,7 +52,7 @@ async def async_setup_platform( async_add_entities_callback(list(ENTITIES.values())) -class MockAlarm(MockEntity, AlarmControlPanel): +class MockAlarm(MockEntity, AlarmControlPanelEntity): """Mock Alarm control panel class.""" def __init__(self, **values): From 8c5c963b9617c3d0419cd6e52e2ba7d6ba9aaba3 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 25 Apr 2020 18:07:15 +0200 Subject: [PATCH 058/511] Rename CoverDevice to CoverEntity (#34595) --- homeassistant/components/abode/cover.py | 4 ++-- homeassistant/components/ads/cover.py | 4 ++-- homeassistant/components/aladdin_connect/cover.py | 4 ++-- homeassistant/components/brunt/cover.py | 4 ++-- homeassistant/components/command_line/cover.py | 4 ++-- homeassistant/components/cover/__init__.py | 13 ++++++++++++- homeassistant/components/deconz/cover.py | 4 ++-- homeassistant/components/demo/cover.py | 4 ++-- homeassistant/components/dynalite/cover.py | 4 ++-- homeassistant/components/esphome/cover.py | 4 ++-- homeassistant/components/fibaro/cover.py | 4 ++-- homeassistant/components/garadget/cover.py | 4 ++-- homeassistant/components/gogogate2/cover.py | 4 ++-- homeassistant/components/group/cover.py | 4 ++-- .../components/homekit_controller/cover.py | 6 +++--- homeassistant/components/homematic/cover.py | 4 ++-- homeassistant/components/homematicip_cloud/cover.py | 10 +++++----- homeassistant/components/insteon/cover.py | 6 +++--- homeassistant/components/isy994/cover.py | 8 ++++---- homeassistant/components/knx/cover.py | 4 ++-- homeassistant/components/lcn/cover.py | 6 +++--- homeassistant/components/lutron/cover.py | 4 ++-- homeassistant/components/lutron_caseta/cover.py | 4 ++-- homeassistant/components/mqtt/cover.py | 4 ++-- homeassistant/components/myq/cover.py | 4 ++-- homeassistant/components/mysensors/cover.py | 4 ++-- homeassistant/components/opengarage/cover.py | 4 ++-- homeassistant/components/rflink/cover.py | 4 ++-- homeassistant/components/rfxtrx/cover.py | 4 ++-- homeassistant/components/rpi_gpio/cover.py | 4 ++-- homeassistant/components/scsgate/cover.py | 4 ++-- homeassistant/components/slide/cover.py | 4 ++-- homeassistant/components/smarthab/cover.py | 4 ++-- homeassistant/components/smartthings/cover.py | 4 ++-- homeassistant/components/soma/cover.py | 4 ++-- homeassistant/components/somfy/cover.py | 4 ++-- homeassistant/components/somfy_mylink/cover.py | 4 ++-- homeassistant/components/supla/cover.py | 6 +++--- homeassistant/components/tahoma/cover.py | 4 ++-- homeassistant/components/tellduslive/cover.py | 4 ++-- homeassistant/components/tellstick/cover.py | 4 ++-- homeassistant/components/template/cover.py | 4 ++-- homeassistant/components/tradfri/cover.py | 4 ++-- homeassistant/components/tuya/cover.py | 4 ++-- homeassistant/components/velbus/cover.py | 4 ++-- homeassistant/components/velux/cover.py | 4 ++-- homeassistant/components/vera/cover.py | 4 ++-- homeassistant/components/wink/cover.py | 10 +++++----- homeassistant/components/xiaomi_aqara/cover.py | 4 ++-- homeassistant/components/zha/cover.py | 4 ++-- homeassistant/components/zwave/cover.py | 6 +++--- tests/components/cover/test_init.py | 12 ++++++++++++ .../testing_config/custom_components/test/cover.py | 4 ++-- 53 files changed, 139 insertions(+), 116 deletions(-) create mode 100644 tests/components/cover/test_init.py diff --git a/homeassistant/components/abode/cover.py b/homeassistant/components/abode/cover.py index 6e38c11cfcc..d88c2fdd404 100644 --- a/homeassistant/components/abode/cover.py +++ b/homeassistant/components/abode/cover.py @@ -1,7 +1,7 @@ """Support for Abode Security System covers.""" import abodepy.helpers.constants as CONST -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverEntity from . import AbodeDevice from .const import DOMAIN @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeCover(AbodeDevice, CoverDevice): +class AbodeCover(AbodeDevice, CoverEntity): """Representation of an Abode cover.""" @property diff --git a/homeassistant/components/ads/cover.py b/homeassistant/components/ads/cover.py index 0fdcbc16ef8..1a350b3e39f 100644 --- a/homeassistant/components/ads/cover.py +++ b/homeassistant/components/ads/cover.py @@ -11,7 +11,7 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -78,7 +78,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class AdsCover(AdsEntity, CoverDevice): +class AdsCover(AdsEntity, CoverEntity): """Representation of ADS cover.""" def __init__( diff --git a/homeassistant/components/aladdin_connect/cover.py b/homeassistant/components/aladdin_connect/cover.py index eaa2dfc85f0..8b61d29b78a 100644 --- a/homeassistant/components/aladdin_connect/cover.py +++ b/homeassistant/components/aladdin_connect/cover.py @@ -8,7 +8,7 @@ from homeassistant.components.cover import ( PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice, + CoverEntity, ) from homeassistant.const import ( CONF_PASSWORD, @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class AladdinDevice(CoverDevice): +class AladdinDevice(CoverEntity): """Representation of Aladdin Connect cover.""" def __init__(self, acc, device): diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index b3a007277c3..83c20ea1088 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -11,7 +11,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class BruntDevice(CoverDevice): +class BruntDevice(CoverEntity): """ Representation of a Brunt cover device. diff --git a/homeassistant/components/command_line/cover.py b/homeassistant/components/command_line/cover.py index 1edf141604f..6f2a038d051 100644 --- a/homeassistant/components/command_line/cover.py +++ b/homeassistant/components/command_line/cover.py @@ -4,7 +4,7 @@ import subprocess import voluptuous as vol -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import ( CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, @@ -63,7 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(covers) -class CommandCover(CoverDevice): +class CommandCover(CoverEntity): """Representation a command line cover.""" def __init__( diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index cb2812f319b..84494e60c6a 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -153,7 +153,7 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class CoverDevice(Entity): +class CoverEntity(Entity): """Representation of a cover.""" @property @@ -318,3 +318,14 @@ class CoverDevice(Entity): await self.async_open_cover_tilt(**kwargs) else: await self.async_close_cover_tilt(**kwargs) + + +class CoverDevice(CoverEntity): + """Representation of a cover (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "CoverDevice is deprecated, modify %s to extend CoverEntity", cls.__name__, + ) diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 7db3477c3bb..e01cfdbe5f8 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -5,7 +5,7 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -46,7 +46,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_cover(gateway.api.lights.values()) -class DeconzCover(DeconzDevice, CoverDevice): +class DeconzCover(DeconzDevice, CoverEntity): """Representation of a deCONZ cover.""" def __init__(self, device, gateway): diff --git a/homeassistant/components/demo/cover.py b/homeassistant/components/demo/cover.py index ab95cc978b3..e65d6e59ece 100644 --- a/homeassistant/components/demo/cover.py +++ b/homeassistant/components/demo/cover.py @@ -4,7 +4,7 @@ from homeassistant.components.cover import ( ATTR_TILT_POSITION, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice, + CoverEntity, ) from homeassistant.core import callback from homeassistant.helpers.event import async_track_utc_time_change @@ -35,7 +35,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoCover(CoverDevice): +class DemoCover(CoverEntity): """Representation of a demo cover.""" def __init__( diff --git a/homeassistant/components/dynalite/cover.py b/homeassistant/components/dynalite/cover.py index dcf16ede58c..a5c25945aa8 100644 --- a/homeassistant/components/dynalite/cover.py +++ b/homeassistant/components/dynalite/cover.py @@ -1,7 +1,7 @@ """Support for the Dynalite channels as covers.""" from typing import Callable -from homeassistant.components.cover import DEVICE_CLASSES, CoverDevice +from homeassistant.components.cover import DEVICE_CLASSES, CoverEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -25,7 +25,7 @@ async def async_setup_entry( ) -class DynaliteCover(DynaliteBase, CoverDevice): +class DynaliteCover(DynaliteBase, CoverEntity): """Representation of a Dynalite Channel as a Home Assistant Cover.""" @property diff --git a/homeassistant/components/esphome/cover.py b/homeassistant/components/esphome/cover.py index 53014991de8..fcf7c22a2a2 100644 --- a/homeassistant/components/esphome/cover.py +++ b/homeassistant/components/esphome/cover.py @@ -14,7 +14,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -39,7 +39,7 @@ async def async_setup_entry( ) -class EsphomeCover(EsphomeEntity, CoverDevice): +class EsphomeCover(EsphomeEntity, CoverEntity): """A cover implementation for ESPHome.""" @property diff --git a/homeassistant/components/fibaro/cover.py b/homeassistant/components/fibaro/cover.py index d2f8094f26d..943df5b5681 100644 --- a/homeassistant/components/fibaro/cover.py +++ b/homeassistant/components/fibaro/cover.py @@ -5,7 +5,7 @@ from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN, - CoverDevice, + CoverEntity, ) from . import FIBARO_DEVICES, FibaroDevice @@ -23,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroCover(FibaroDevice, CoverDevice): +class FibaroCover(FibaroDevice, CoverEntity): """Representation a Fibaro Cover.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index a1f324be1ff..34a9a13b8d9 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import ( CONF_ACCESS_TOKEN, CONF_COVERS, @@ -74,7 +74,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(covers) -class GaradgetCover(CoverDevice): +class GaradgetCover(CoverEntity): """Representation of a Garadget cover.""" def __init__(self, hass, args): diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 62aea62bf84..68babd3debe 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -4,7 +4,7 @@ import logging from pygogogate2 import Gogogate2API as pygogogate2 import voluptuous as vol -from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverDevice +from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, @@ -57,7 +57,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class MyGogogate2Device(CoverDevice): +class MyGogogate2Device(CoverEntity): """Representation of a Gogogate2 cover.""" def __init__(self, mygogogate2, device, name): diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index d9efdfa53c6..c4e691eeff9 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -27,7 +27,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, - CoverDevice, + CoverEntity, ) from homeassistant.const import ( ATTR_ASSUMED_STATE, @@ -66,7 +66,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) -class CoverGroup(CoverDevice): +class CoverGroup(CoverEntity): """Representation of a CoverGroup.""" def __init__(self, name, entities): diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 88885d49b8e..086f780b816 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -13,7 +13,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.core import callback @@ -58,7 +58,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): conn.add_listener(async_add_service) -class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): +class HomeKitGarageDoorCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Garage Door.""" @property @@ -128,7 +128,7 @@ class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice): return attributes -class HomeKitWindowCover(HomeKitEntity, CoverDevice): +class HomeKitWindowCover(HomeKitEntity, CoverEntity): """Representation of a HomeKit Window or Window Covering.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/cover.py b/homeassistant/components/homematic/cover.py index 0dea1181d73..a520c08e478 100644 --- a/homeassistant/components/homematic/cover.py +++ b/homeassistant/components/homematic/cover.py @@ -4,7 +4,7 @@ import logging from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - CoverDevice, + CoverEntity, ) from .const import ATTR_DISCOVER_DEVICES @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMCover(HMDevice, CoverDevice): +class HMCover(HMDevice, CoverEntity): """Representation a HomeMatic Cover.""" @property diff --git a/homeassistant/components/homematicip_cloud/cover.py b/homeassistant/components/homematicip_cloud/cover.py index 768c893a100..580e2d21a11 100644 --- a/homeassistant/components/homematicip_cloud/cover.py +++ b/homeassistant/components/homematicip_cloud/cover.py @@ -13,7 +13,7 @@ from homematicip.base.enums import DoorCommand, DoorState from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - CoverDevice, + CoverEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -51,7 +51,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice): +class HomematicipCoverShutter(HomematicipGenericDevice, CoverEntity): """Representation of a HomematicIP Cloud cover shutter device.""" @property @@ -88,7 +88,7 @@ class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice): await self._device.set_shutter_stop() -class HomematicipCoverSlats(HomematicipCoverShutter, CoverDevice): +class HomematicipCoverSlats(HomematicipCoverShutter, CoverEntity): """Representation of a HomematicIP Cloud cover slats device.""" @property @@ -118,7 +118,7 @@ class HomematicipCoverSlats(HomematicipCoverShutter, CoverDevice): await self._device.set_shutter_stop() -class HomematicipGarageDoorModuleTormatic(HomematicipGenericDevice, CoverDevice): +class HomematicipGarageDoorModuleTormatic(HomematicipGenericDevice, CoverEntity): """Representation of a HomematicIP Garage Door Module for Tormatic.""" @property @@ -150,7 +150,7 @@ class HomematicipGarageDoorModuleTormatic(HomematicipGenericDevice, CoverDevice) await self._device.send_door_command(DoorCommand.STOP) -class HomematicipCoverShutterGroup(HomematicipCoverSlats, CoverDevice): +class HomematicipCoverShutterGroup(HomematicipCoverSlats, CoverEntity): """Representation of a HomematicIP Cloud cover shutter group.""" def __init__(self, hap: HomematicipHAP, device, post: str = "ShutterGroup") -> None: diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 575799cbf67..b325a6ebd84 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -7,7 +7,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from .insteon_entity import InsteonEntity @@ -34,12 +34,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= device.states[state_key].name, ) - new_entity = InsteonCoverDevice(device, state_key) + new_entity = InsteonCoverEntity(device, state_key) async_add_entities([new_entity]) -class InsteonCoverDevice(InsteonEntity, CoverDevice): +class InsteonCoverEntity(InsteonEntity, CoverEntity): """A Class for an Insteon device.""" @property diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index f5e052f6926..38688db21d2 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -2,7 +2,7 @@ import logging from typing import Callable -from homeassistant.components.cover import DOMAIN, CoverDevice +from homeassistant.components.cover import DOMAIN, CoverEntity from homeassistant.const import ( STATE_CLOSED, STATE_CLOSING, @@ -31,7 +31,7 @@ def setup_platform( """Set up the ISY994 cover platform.""" devices = [] for node in hass.data[ISY994_NODES][DOMAIN]: - devices.append(ISYCoverDevice(node)) + devices.append(ISYCoverEntity(node)) for name, status, actions in hass.data[ISY994_PROGRAMS][DOMAIN]: devices.append(ISYCoverProgram(name, status, actions)) @@ -39,7 +39,7 @@ def setup_platform( add_entities(devices) -class ISYCoverDevice(ISYDevice, CoverDevice): +class ISYCoverEntity(ISYDevice, CoverEntity): """Representation of an ISY994 cover device.""" @property @@ -72,7 +72,7 @@ class ISYCoverDevice(ISYDevice, CoverDevice): _LOGGER.error("Unable to close the cover") -class ISYCoverProgram(ISYCoverDevice): +class ISYCoverProgram(ISYCoverEntity): """Representation of an ISY994 cover program.""" def __init__(self, name: str, node: object, actions: object) -> None: diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 5c4aa762b5c..92e7e599bcb 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -11,7 +11,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.const import CONF_NAME from homeassistant.core import callback @@ -94,7 +94,7 @@ def async_add_entities_config(hass, config, async_add_entities): async_add_entities([KNXCover(cover)]) -class KNXCover(CoverDevice): +class KNXCover(CoverEntity): """Representation of a KNX cover.""" def __init__(self, device): diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 61ae05fa010..05ee17a7daf 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -1,7 +1,7 @@ """Support for LCN covers.""" import pypck -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverEntity from homeassistant.const import CONF_ADDRESS from . import LcnDevice @@ -32,7 +32,7 @@ async def async_setup_platform( async_add_entities(devices) -class LcnOutputsCover(LcnDevice, CoverDevice): +class LcnOutputsCover(LcnDevice, CoverEntity): """Representation of a LCN cover connected to output ports.""" def __init__(self, config, address_connection): @@ -111,7 +111,7 @@ class LcnOutputsCover(LcnDevice, CoverDevice): self.async_write_ha_state() -class LcnRelayCover(LcnDevice, CoverDevice): +class LcnRelayCover(LcnDevice, CoverEntity): """Representation of a LCN cover connected to relays.""" def __init__(self, config, address_connection): diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index cf8d4d16427..438a433fb0f 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -6,7 +6,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class LutronCover(LutronDevice, CoverDevice): +class LutronCover(LutronDevice, CoverEntity): """Representation of a Lutron shade.""" @property diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 60c723b7b42..64c0aeac744 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -7,7 +7,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice @@ -27,7 +27,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities, True) -class LutronCasetaCover(LutronCasetaDevice, CoverDevice): +class LutronCasetaCover(LutronCasetaDevice, CoverEntity): """Representation of a Lutron shade.""" @property diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index d5487bbe29f..4ed128f1ff3 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -16,7 +16,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, - CoverDevice, + CoverEntity, ) from homeassistant.const import ( CONF_DEVICE, @@ -205,7 +205,7 @@ class MqttCover( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - CoverDevice, + CoverEntity, ): """Representation of a cover that can be controlled using MQTT.""" diff --git a/homeassistant/components/myq/cover.py b/homeassistant/components/myq/cover.py index 04eb49c00a9..e351eca81ca 100644 --- a/homeassistant/components/myq/cover.py +++ b/homeassistant/components/myq/cover.py @@ -10,7 +10,7 @@ from homeassistant.components.cover import ( PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice, + CoverEntity, ) from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( @@ -80,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class MyQDevice(CoverDevice): +class MyQDevice(CoverEntity): """Representation of a MyQ cover.""" def __init__(self, coordinator, device): diff --git a/homeassistant/components/mysensors/cover.py b/homeassistant/components/mysensors/cover.py index b60cf9457a9..f2ede69793f 100644 --- a/homeassistant/components/mysensors/cover.py +++ b/homeassistant/components/mysensors/cover.py @@ -1,6 +1,6 @@ """Support for MySensors covers.""" from homeassistant.components import mysensors -from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity from homeassistant.const import STATE_OFF, STATE_ON @@ -15,7 +15,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): +class MySensorsCover(mysensors.device.MySensorsEntity, CoverEntity): """Representation of the value of a MySensors Cover child node.""" @property diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index e34f98c87d2..5297d0ce05e 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -9,7 +9,7 @@ from homeassistant.components.cover import ( PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice, + CoverEntity, ) from homeassistant.const import ( CONF_COVERS, @@ -74,7 +74,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(covers, True) -class OpenGarageCover(CoverDevice): +class OpenGarageCover(CoverEntity): """Representation of a OpenGarage cover.""" def __init__(self, args): diff --git a/homeassistant/components/rflink/cover.py b/homeassistant/components/rflink/cover.py index 794542cb9d4..5eacce3afa8 100644 --- a/homeassistant/components/rflink/cover.py +++ b/homeassistant/components/rflink/cover.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import CONF_NAME, CONF_TYPE, STATE_OPEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity @@ -112,7 +112,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices_from_config(config)) -class RflinkCover(RflinkCommand, CoverDevice, RestoreEntity): +class RflinkCover(RflinkCommand, CoverEntity, RestoreEntity): """Rflink entity which can switch on/stop/off (eg: cover).""" async def async_added_to_hass(self): diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index da19c42ed69..bd64d20fe46 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -2,7 +2,7 @@ import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import CONF_NAME, STATE_OPEN from homeassistant.helpers import config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity @@ -63,7 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(cover_update) -class RfxtrxCover(RfxtrxDevice, CoverDevice, RestoreEntity): +class RfxtrxCover(RfxtrxDevice, CoverEntity, RestoreEntity): """Representation of a RFXtrx cover.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/rpi_gpio/cover.py b/homeassistant/components/rpi_gpio/cover.py index 648171b9738..56e76959ecc 100644 --- a/homeassistant/components/rpi_gpio/cover.py +++ b/homeassistant/components/rpi_gpio/cover.py @@ -5,7 +5,7 @@ from time import sleep import voluptuous as vol from homeassistant.components import rpi_gpio -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -71,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(covers) -class RPiGPIOCover(CoverDevice): +class RPiGPIOCover(CoverEntity): """Representation of a Raspberry GPIO cover.""" def __init__( diff --git a/homeassistant/components/scsgate/cover.py b/homeassistant/components/scsgate/cover.py index f9ef2e12730..0c7d057316c 100644 --- a/homeassistant/components/scsgate/cover.py +++ b/homeassistant/components/scsgate/cover.py @@ -8,7 +8,7 @@ from scsgate.tasks import ( ) import voluptuous as vol -from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice +from homeassistant.components.cover import PLATFORM_SCHEMA, CoverEntity from homeassistant.const import CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(covers) -class SCSGateCover(CoverDevice): +class SCSGateCover(CoverEntity): """Representation of SCSGate cover.""" def __init__(self, scs_id, name, logger, scsgate): diff --git a/homeassistant/components/slide/cover.py b/homeassistant/components/slide/cover.py index f50226b9f01..470cf9e5a1f 100644 --- a/homeassistant/components/slide/cover.py +++ b/homeassistant/components/slide/cover.py @@ -8,7 +8,7 @@ from homeassistant.components.cover import ( STATE_CLOSED, STATE_CLOSING, STATE_OPENING, - CoverDevice, + CoverEntity, ) from homeassistant.const import ATTR_ID @@ -32,7 +32,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class SlideCover(CoverDevice): +class SlideCover(CoverEntity): """Representation of a Slide cover.""" def __init__(self, api, slide): diff --git a/homeassistant/components/smarthab/cover.py b/homeassistant/components/smarthab/cover.py index af55f2de7f9..09b8a7435ee 100644 --- a/homeassistant/components/smarthab/cover.py +++ b/homeassistant/components/smarthab/cover.py @@ -10,7 +10,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from . import DATA_HUB, DOMAIN @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SmartHabCover(CoverDevice): +class SmartHabCover(CoverEntity): """Representation a cover.""" def __init__(self, cover): diff --git a/homeassistant/components/smartthings/cover.py b/homeassistant/components/smartthings/cover.py index a41d9d6b9f7..ddc52ec3f6c 100644 --- a/homeassistant/components/smartthings/cover.py +++ b/homeassistant/components/smartthings/cover.py @@ -16,7 +16,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, - CoverDevice, + CoverEntity, ) from homeassistant.const import ATTR_BATTERY_LEVEL @@ -61,7 +61,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: return None -class SmartThingsCover(SmartThingsEntity, CoverDevice): +class SmartThingsCover(SmartThingsEntity, CoverEntity): """Define a SmartThings cover.""" def __init__(self, device): diff --git a/homeassistant/components/soma/cover.py b/homeassistant/components/soma/cover.py index 9bfe903e724..f2929dd8ddd 100644 --- a/homeassistant/components/soma/cover.py +++ b/homeassistant/components/soma/cover.py @@ -2,7 +2,7 @@ import logging -from homeassistant.components.cover import ATTR_POSITION, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, CoverEntity from homeassistant.components.soma import API, DEVICES, DOMAIN, SomaEntity _LOGGER = logging.getLogger(__name__) @@ -18,7 +18,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class SomaCover(SomaEntity, CoverDevice): +class SomaCover(SomaEntity, CoverEntity): """Representation of a Soma cover device.""" def close_cover(self, **kwargs): diff --git a/homeassistant/components/somfy/cover.py b/homeassistant/components/somfy/cover.py index d0e555ed55c..cddde87d8f6 100644 --- a/homeassistant/components/somfy/cover.py +++ b/homeassistant/components/somfy/cover.py @@ -5,7 +5,7 @@ from pymfy.api.devices.category import Category from homeassistant.components.cover import ( ATTR_POSITION, ATTR_TILT_POSITION, - CoverDevice, + CoverEntity, ) from . import API, CONF_OPTIMISTIC, DEVICES, DOMAIN, SomfyEntity @@ -35,7 +35,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(await hass.async_add_executor_job(get_covers), True) -class SomfyCover(SomfyEntity, CoverDevice): +class SomfyCover(SomfyEntity, CoverEntity): """Representation of a Somfy cover device.""" def __init__(self, device, api, optimistic): diff --git a/homeassistant/components/somfy_mylink/cover.py b/homeassistant/components/somfy_mylink/cover.py index b4680cc06de..767abda2fd7 100644 --- a/homeassistant/components/somfy_mylink/cover.py +++ b/homeassistant/components/somfy_mylink/cover.py @@ -1,7 +1,7 @@ """Cover Platform for the Somfy MyLink component.""" import logging -from homeassistant.components.cover import ENTITY_ID_FORMAT, CoverDevice +from homeassistant.components.cover import ENTITY_ID_FORMAT, CoverEntity from homeassistant.util import slugify from . import CONF_DEFAULT_REVERSE, DATA_SOMFY_MYLINK @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(cover_list) -class SomfyShade(CoverDevice): +class SomfyShade(CoverEntity): """Object for controlling a Somfy cover.""" def __init__( diff --git a/homeassistant/components/supla/cover.py b/homeassistant/components/supla/cover.py index 659b78cc41a..1c0f2f60431 100644 --- a/homeassistant/components/supla/cover.py +++ b/homeassistant/components/supla/cover.py @@ -5,7 +5,7 @@ from pprint import pformat from homeassistant.components.cover import ( ATTR_POSITION, DEVICE_CLASS_GARAGE, - CoverDevice, + CoverEntity, ) from homeassistant.components.supla import SuplaChannel @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities) -class SuplaCover(SuplaChannel, CoverDevice): +class SuplaCover(SuplaChannel, CoverEntity): """Representation of a Supla Cover.""" @property @@ -67,7 +67,7 @@ class SuplaCover(SuplaChannel, CoverDevice): self.action("STOP") -class SuplaGateDoor(SuplaChannel, CoverDevice): +class SuplaGateDoor(SuplaChannel, CoverEntity): """Representation of a Supla gate door.""" @property diff --git a/homeassistant/components/tahoma/cover.py b/homeassistant/components/tahoma/cover.py index e13f9bb8859..2eec9160811 100644 --- a/homeassistant/components/tahoma/cover.py +++ b/homeassistant/components/tahoma/cover.py @@ -10,7 +10,7 @@ from homeassistant.components.cover import ( DEVICE_CLASS_GARAGE, DEVICE_CLASS_SHUTTER, DEVICE_CLASS_WINDOW, - CoverDevice, + CoverEntity, ) from homeassistant.util.dt import utcnow @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaCover(TahomaDevice, CoverDevice): +class TahomaCover(TahomaDevice, CoverEntity): """Representation a Tahoma Cover.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/tellduslive/cover.py b/homeassistant/components/tellduslive/cover.py index 6e31cd595bf..246b22dc157 100644 --- a/homeassistant/components/tellduslive/cover.py +++ b/homeassistant/components/tellduslive/cover.py @@ -2,7 +2,7 @@ import logging from homeassistant.components import cover, tellduslive -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from .entry import TelldusLiveEntity @@ -25,7 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TelldusLiveCover(TelldusLiveEntity, CoverDevice): +class TelldusLiveCover(TelldusLiveEntity, CoverEntity): """Representation of a cover.""" @property diff --git a/homeassistant/components/tellstick/cover.py b/homeassistant/components/tellstick/cover.py index 0a5643fc1ea..bb25a601a2f 100644 --- a/homeassistant/components/tellstick/cover.py +++ b/homeassistant/components/tellstick/cover.py @@ -1,5 +1,5 @@ """Support for Tellstick covers.""" -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverEntity from . import ( ATTR_DISCOVER_CONFIG, @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class TellstickCover(TellstickDevice, CoverDevice): +class TellstickCover(TellstickDevice, CoverEntity): """Representation of a Tellstick cover.""" @property diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 3e3232f2b91..cea91ea2963 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -17,7 +17,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, - CoverDevice, + CoverEntity, ) from homeassistant.const import ( CONF_DEVICE_CLASS, @@ -161,7 +161,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(covers) -class CoverTemplate(CoverDevice): +class CoverTemplate(CoverEntity): """Representation of a Template cover.""" def __init__( diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 744ba2e13b1..6d8669eea91 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -1,6 +1,6 @@ """Support for IKEA Tradfri covers.""" -from homeassistant.components.cover import ATTR_POSITION, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, CoverEntity from .base_class import TradfriBaseDevice from .const import ATTR_MODEL, CONF_GATEWAY_ID, KEY_API, KEY_GATEWAY @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(TradfriCover(cover, api, gateway_id) for cover in covers) -class TradfriCover(TradfriBaseDevice, CoverDevice): +class TradfriCover(TradfriBaseDevice, CoverEntity): """The platform class required by Home Assistant.""" def __init__(self, device, api, gateway_id): diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index 35fd4719fdb..d7528cf6092 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -4,7 +4,7 @@ from homeassistant.components.cover import ( SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from . import DATA_TUYA, TuyaDevice @@ -27,7 +27,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class TuyaCover(TuyaDevice, CoverDevice): +class TuyaCover(TuyaDevice, CoverEntity): """Tuya cover devices.""" def __init__(self, tuya): diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 4478bb81c3c..efe4fdc964b 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -9,7 +9,7 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from . import VelbusEntity @@ -29,7 +29,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusCover(VelbusEntity, CoverDevice): +class VelbusCover(VelbusEntity, CoverEntity): """Representation a Velbus cover.""" @property diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index fe5b1dcf3af..52d2e497b7d 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -8,7 +8,7 @@ from homeassistant.components.cover import ( SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_STOP, - CoverDevice, + CoverEntity, ) from homeassistant.core import callback @@ -25,7 +25,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class VeluxCover(CoverDevice): +class VeluxCover(CoverEntity): """Representation of a Velux cover.""" def __init__(self, node): diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index 0d0edb841c1..a1f536d9cc1 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -6,7 +6,7 @@ from homeassistant.components.cover import ( ATTR_POSITION, DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT, - CoverDevice, + CoverEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -33,7 +33,7 @@ async def async_setup_entry( ) -class VeraCover(VeraDevice, CoverDevice): +class VeraCover(VeraDevice, CoverEntity): """Representation a Vera Cover.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/wink/cover.py b/homeassistant/components/wink/cover.py index 1ce7f9b8875..f2f4241c64d 100644 --- a/homeassistant/components/wink/cover.py +++ b/homeassistant/components/wink/cover.py @@ -1,7 +1,7 @@ """Support for Wink covers.""" import pywink -from homeassistant.components.cover import ATTR_POSITION, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, CoverEntity from . import DOMAIN, WinkDevice @@ -12,18 +12,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for shade in pywink.get_shades(): _id = shade.object_id() + shade.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkCoverDevice(shade, hass)]) + add_entities([WinkCoverEntity(shade, hass)]) for shade in pywink.get_shade_groups(): _id = shade.object_id() + shade.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkCoverDevice(shade, hass)]) + add_entities([WinkCoverEntity(shade, hass)]) for door in pywink.get_garage_doors(): _id = door.object_id() + door.name() if _id not in hass.data[DOMAIN]["unique_ids"]: - add_entities([WinkCoverDevice(door, hass)]) + add_entities([WinkCoverEntity(door, hass)]) -class WinkCoverDevice(WinkDevice, CoverDevice): +class WinkCoverEntity(WinkDevice, CoverEntity): """Representation of a Wink cover device.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/xiaomi_aqara/cover.py b/homeassistant/components/xiaomi_aqara/cover.py index da6b24d616a..52d2487e74f 100644 --- a/homeassistant/components/xiaomi_aqara/cover.py +++ b/homeassistant/components/xiaomi_aqara/cover.py @@ -1,7 +1,7 @@ """Support for Xiaomi curtain.""" import logging -from homeassistant.components.cover import ATTR_POSITION, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, CoverEntity from . import PY_XIAOMI_GATEWAY, XiaomiDevice @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class XiaomiGenericCover(XiaomiDevice, CoverDevice): +class XiaomiGenericCover(XiaomiDevice, CoverEntity): """Representation of a XiaomiGenericCover.""" def __init__(self, device, name, data_key, xiaomi_hub): diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 571741da7c3..0feaf14b3c5 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -5,7 +5,7 @@ import logging from zigpy.zcl.foundation import Status -from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice +from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -42,7 +42,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @STRICT_MATCH(channel_names=CHANNEL_COVER) -class ZhaCover(ZhaEntity, CoverDevice): +class ZhaCover(ZhaEntity, CoverEntity): """Representation of a ZHA cover.""" def __init__(self, unique_id, zha_device, channels, **kwargs): diff --git a/homeassistant/components/zwave/cover.py b/homeassistant/components/zwave/cover.py index e6aa8028849..688ee666676 100644 --- a/homeassistant/components/zwave/cover.py +++ b/homeassistant/components/zwave/cover.py @@ -6,7 +6,7 @@ from homeassistant.components.cover import ( DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, - CoverDevice, + CoverEntity, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -56,7 +56,7 @@ def get_device(hass, values, node_config, **kwargs): return None -class ZwaveRollershutter(ZWaveDeviceEntity, CoverDevice): +class ZwaveRollershutter(ZWaveDeviceEntity, CoverEntity): """Representation of an Z-Wave cover.""" def __init__(self, hass, values, invert_buttons, invert_percent): @@ -140,7 +140,7 @@ class ZwaveRollershutter(ZWaveDeviceEntity, CoverDevice): self._network.manager.releaseButton(self._open_id) -class ZwaveGarageDoorBase(ZWaveDeviceEntity, CoverDevice): +class ZwaveGarageDoorBase(ZWaveDeviceEntity, CoverEntity): """Base class for a Zwave garage door device.""" def __init__(self, values): diff --git a/tests/components/cover/test_init.py b/tests/components/cover/test_init.py new file mode 100644 index 00000000000..df8df2c4bf1 --- /dev/null +++ b/tests/components/cover/test_init.py @@ -0,0 +1,12 @@ +"""The tests for Cover.""" +import homeassistant.components.cover as cover + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomCover(cover.CoverDevice): + pass + + CustomCover() + assert "CoverDevice is deprecated, modify CustomCover" in caplog.text diff --git a/tests/testing_config/custom_components/test/cover.py b/tests/testing_config/custom_components/test/cover.py index bdaacfa4e3c..f7b0dae15ee 100644 --- a/tests/testing_config/custom_components/test/cover.py +++ b/tests/testing_config/custom_components/test/cover.py @@ -12,7 +12,7 @@ from homeassistant.components.cover import ( SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, SUPPORT_STOP_TILT, - CoverDevice, + CoverEntity, ) from tests.common import MockEntity @@ -65,7 +65,7 @@ async def async_setup_platform( async_add_entities_callback(ENTITIES) -class MockCover(MockEntity, CoverDevice): +class MockCover(MockEntity, CoverEntity): """Mock Cover class.""" @property From d53cb951e7e9588c152ec09446047de490c4fad5 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Sat, 25 Apr 2020 22:15:31 +0200 Subject: [PATCH 059/511] Fix zero value state rendering sensor unavailable (#34694) Co-Authored-By: Franck Nijhof --- homeassistant/components/netatmo/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index be7e0f3e971..327d9ecc52f 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -223,7 +223,7 @@ class NetatmoSensor(Entity): @property def available(self): """Return True if entity is available.""" - return bool(self._state) + return self._state is not None def update(self): """Get the latest data from Netatmo API and updates the states.""" From 0d700f6a636c4fbc6d00d1ea84fdf806c4e52668 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Apr 2020 14:32:55 -0700 Subject: [PATCH 060/511] Fix tests for Python 3.8 (#34672) --- .../ambiclimate/test_config_flow.py | 3 +- tests/components/awair/test_sensor.py | 24 ++++----- tests/components/axis/test_config_flow.py | 10 ++-- tests/components/cast/test_media_player.py | 9 ++-- tests/components/cloud/test_account_link.py | 18 +++---- tests/components/config/test_core.py | 38 ++++++-------- .../components/coolmaster/test_config_flow.py | 9 ++-- tests/components/device_tracker/test_init.py | 3 +- tests/components/freebox/test_config_flow.py | 8 ++- tests/components/hassio/test_init.py | 9 ++-- tests/components/hisense_aehw4a1/test_init.py | 18 +++---- tests/components/homeassistant/test_init.py | 21 ++++---- tests/components/ipma/test_weather.py | 13 ++--- tests/components/izone/test_config_flow.py | 14 ++---- .../components/luftdaten/test_config_flow.py | 19 +++---- tests/components/microsoft_face/test_init.py | 26 ++++------ tests/components/mikrotik/test_init.py | 15 +++--- tests/components/mqtt/test_init.py | 50 +++++++++---------- tests/components/mqtt/test_light.py | 50 +++++++++---------- tests/components/mqtt/test_light_json.py | 38 +++++++------- tests/components/mqtt/test_light_template.py | 6 +-- tests/components/notion/test_config_flow.py | 3 +- .../openalpr_cloud/test_image_processing.py | 3 +- .../openalpr_local/test_image_processing.py | 2 +- .../opentherm_gw/test_config_flow.py | 2 +- .../components/owntracks/test_config_flow.py | 3 +- .../rainmachine/test_config_flow.py | 3 +- tests/components/ring/test_config_flow.py | 2 +- tests/components/rmvtransport/test_sensor.py | 19 +++---- tests/components/sentry/test_config_flow.py | 3 +- tests/components/shell_command/test_init.py | 3 +- tests/components/smhi/test_config_flow.py | 3 +- tests/components/smhi/test_weather.py | 3 +- tests/components/solarlog/test_config_flow.py | 3 +- tests/components/tesla/test_config_flow.py | 3 +- tests/components/tradfri/conftest.py | 7 +-- tests/components/tradfri/test_config_flow.py | 49 ++++++++++-------- tests/components/tradfri/test_init.py | 13 +++-- tests/components/upnp/test_init.py | 3 +- tests/conftest.py | 5 +- 40 files changed, 248 insertions(+), 285 deletions(-) diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index acf3717b898..045eac1328b 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -1,7 +1,6 @@ """Tests for the Ambiclimate config flow.""" -from unittest.mock import Mock, patch - import ambiclimate +from asynctest import Mock, patch from homeassistant import data_entry_flow from homeassistant.components.ambiclimate import config_flow diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 2d88a5019b1..8ae5bb8017f 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -4,7 +4,8 @@ from contextlib import contextmanager from datetime import timedelta import json import logging -from unittest.mock import patch + +from asynctest import patch from homeassistant.components.awair.sensor import ( ATTR_LAST_API_UPDATE, @@ -28,7 +29,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from homeassistant.util.dt import parse_datetime, utcnow -from tests.common import async_fire_time_changed, load_fixture, mock_coro +from tests.common import async_fire_time_changed, load_fixture DISCOVERY_CONFIG = {"sensor": {"platform": "awair", "access_token": "qwerty"}} @@ -68,9 +69,9 @@ def alter_time(retval): async def setup_awair(hass, config=None, data_fixture=AIR_DATA_FIXTURE): """Load the Awair platform.""" devices_json = json.loads(load_fixture("awair_devices.json")) - devices_mock = mock_coro(devices_json) + devices_mock = devices_json devices_patch = patch("python_awair.AwairClient.devices", return_value=devices_mock) - air_data_mock = mock_coro(data_fixture) + air_data_mock = data_fixture air_data_patch = patch( "python_awair.AwairClient.air_data_latest", return_value=air_data_mock ) @@ -233,8 +234,7 @@ async def test_availability(hass): future = NOW + timedelta(minutes=30) data_patch = patch( - "python_awair.AwairClient.air_data_latest", - return_value=mock_coro(AIR_DATA_FIXTURE), + "python_awair.AwairClient.air_data_latest", return_value=AIR_DATA_FIXTURE, ) with data_patch, alter_time(future): @@ -246,9 +246,7 @@ async def test_availability(hass): future = NOW + timedelta(hours=1) fixture = AIR_DATA_FIXTURE_UPDATED fixture[0][ATTR_TIMESTAMP] = str(future) - data_patch = patch( - "python_awair.AwairClient.air_data_latest", return_value=mock_coro(fixture) - ) + data_patch = patch("python_awair.AwairClient.air_data_latest", return_value=fixture) with data_patch, alter_time(future): async_fire_time_changed(hass, future) @@ -258,9 +256,7 @@ async def test_availability(hass): future = NOW + timedelta(minutes=90) fixture = AIR_DATA_FIXTURE_EMPTY - data_patch = patch( - "python_awair.AwairClient.air_data_latest", return_value=mock_coro(fixture) - ) + data_patch = patch("python_awair.AwairClient.air_data_latest", return_value=fixture) with data_patch, alter_time(future): async_fire_time_changed(hass, future) @@ -276,7 +272,7 @@ async def test_async_update(hass): future = NOW + timedelta(minutes=10) data_patch = patch( "python_awair.AwairClient.air_data_latest", - return_value=mock_coro(AIR_DATA_FIXTURE_UPDATED), + return_value=AIR_DATA_FIXTURE_UPDATED, ) with data_patch, alter_time(future): @@ -300,7 +296,7 @@ async def test_throttle_async_update(hass): future = NOW + timedelta(minutes=1) data_patch = patch( "python_awair.AwairClient.air_data_latest", - return_value=mock_coro(AIR_DATA_FIXTURE_UPDATED), + return_value=AIR_DATA_FIXTURE_UPDATED, ) with data_patch, alter_time(future): diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 2e4c3e9f8be..e0bf06e3468 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,12 +1,12 @@ """Test Axis config flow.""" -from unittest.mock import Mock, patch +from asynctest import Mock, patch from homeassistant.components import axis from homeassistant.components.axis import config_flow from .test_device import MAC, MODEL, NAME, setup_axis_integration -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry def setup_mock_axis_device(mock_device): @@ -80,7 +80,7 @@ async def test_manual_configuration_update_configuration(hass): with patch( "homeassistant.components.axis.config_flow.get_device", - return_value=mock_coro(mock_device), + return_value=mock_device, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -113,7 +113,7 @@ async def test_flow_fails_already_configured(hass): with patch( "homeassistant.components.axis.config_flow.get_device", - return_value=mock_coro(mock_device), + return_value=mock_device, ): result = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -232,7 +232,7 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): async def test_zeroconf_flow(hass): """Test that zeroconf discovery for new devices work.""" - with patch.object(axis, "get_device", return_value=mock_coro(Mock())): + with patch.object(axis, "get_device", return_value=Mock()): result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, data={ diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 41cc50f33ae..e2c1064218d 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -1,9 +1,9 @@ """The tests for the Cast Media player platform.""" # pylint: disable=protected-access from typing import Optional -from unittest.mock import MagicMock, Mock, patch from uuid import UUID +from asynctest import MagicMock, Mock, patch import attr import pytest @@ -14,7 +14,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry @pytest.fixture(autouse=True) @@ -468,7 +468,6 @@ async def test_entry_setup_no_config(hass: HomeAssistantType): with patch( "homeassistant.components.cast.media_player._async_setup_platform", - return_value=mock_coro(), ) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -484,7 +483,6 @@ async def test_entry_setup_single_config(hass: HomeAssistantType): with patch( "homeassistant.components.cast.media_player._async_setup_platform", - return_value=mock_coro(), ) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -500,7 +498,6 @@ async def test_entry_setup_list_config(hass: HomeAssistantType): with patch( "homeassistant.components.cast.media_player._async_setup_platform", - return_value=mock_coro(), ) as mock_setup: await cast.async_setup_entry(hass, MockConfigEntry(), None) @@ -517,7 +514,7 @@ async def test_entry_setup_platform_not_ready(hass: HomeAssistantType): with patch( "homeassistant.components.cast.media_player._async_setup_platform", - return_value=mock_coro(exception=Exception), + side_effect=Exception, ) as mock_setup: with pytest.raises(PlatformNotReady): await cast.async_setup_entry(hass, MockConfigEntry(), None) diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index a8c247cc985..a5f3bef7353 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -2,8 +2,8 @@ import asyncio import logging from time import time -from unittest.mock import Mock, patch +from asynctest import CoroutineMock, Mock, patch import pytest from homeassistant import config_entries, data_entry_flow @@ -11,7 +11,7 @@ from homeassistant.components.cloud import account_link from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.util.dt import utcnow -from tests.common import async_fire_time_changed, mock_coro, mock_platform +from tests.common import async_fire_time_changed, mock_platform TEST_DOMAIN = "oauth2_test" @@ -42,12 +42,10 @@ async def test_setup_provide_implementation(hass): with patch( "homeassistant.components.cloud.account_link._get_services", - side_effect=lambda _: mock_coro( - [ - {"service": "test", "min_version": "0.1.0"}, - {"service": "too_new", "min_version": "100.0.0"}, - ] - ), + return_value=[ + {"service": "test", "min_version": "0.1.0"}, + {"service": "too_new", "min_version": "100.0.0"}, + ], ): assert ( await config_entry_oauth2_flow.async_get_implementations( @@ -77,7 +75,7 @@ async def test_get_services_cached(hass): with patch.object(account_link, "CACHE_TIMEOUT", 0), patch( "hass_nabucasa.account_link.async_fetch_available_services", - side_effect=lambda _: mock_coro(services), + side_effect=lambda _: services, ) as mock_fetch: assert await account_link._get_services(hass) == 1 @@ -111,7 +109,7 @@ async def test_implementation(hass, flow_handler): flow_finished = asyncio.Future() helper = Mock( - async_get_authorize_url=Mock(return_value=mock_coro("http://example.com/auth")), + async_get_authorize_url=CoroutineMock(return_value="http://example.com/auth"), async_get_tokens=Mock(return_value=flow_finished), ) diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index 8caa0f3e6fb..a722333c037 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -1,6 +1,5 @@ """Test hassbian config.""" -from unittest.mock import patch - +from asynctest import patch import pytest from homeassistant.bootstrap import async_setup_component @@ -9,8 +8,6 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_IMPERIAL from homeassistant.util import dt as dt_util, location -from tests.common import mock_coro - ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE @@ -31,7 +28,7 @@ async def test_validate_config_ok(hass, hass_client): with patch( "homeassistant.components.config.core.async_check_ha_config_file", - return_value=mock_coro(), + return_value=None, ): resp = await client.post("/api/config/core/check_config") @@ -42,7 +39,7 @@ async def test_validate_config_ok(hass, hass_client): with patch( "homeassistant.components.config.core.async_check_ha_config_file", - return_value=mock_coro("beer"), + return_value="beer", ): resp = await client.post("/api/config/core/check_config") @@ -121,8 +118,7 @@ async def test_websocket_bad_core_update(hass, client): async def test_detect_config(hass, client): """Test detect config.""" with patch( - "homeassistant.util.location.async_detect_location_info", - return_value=mock_coro(None), + "homeassistant.util.location.async_detect_location_info", return_value=None, ): await client.send_json({"id": 1, "type": "config/core/detect"}) @@ -136,20 +132,18 @@ async def test_detect_config_fail(hass, client): """Test detect config.""" with patch( "homeassistant.util.location.async_detect_location_info", - return_value=mock_coro( - location.LocationInfo( - ip=None, - country_code=None, - country_name=None, - region_code=None, - region_name=None, - city=None, - zip_code=None, - latitude=None, - longitude=None, - use_metric=True, - time_zone="Europe/Amsterdam", - ) + return_value=location.LocationInfo( + ip=None, + country_code=None, + country_name=None, + region_code=None, + region_name=None, + city=None, + zip_code=None, + latitude=None, + longitude=None, + use_metric=True, + time_zone="Europe/Amsterdam", ), ): await client.send_json({"id": 1, "type": "config/core/detect"}) diff --git a/tests/components/coolmaster/test_config_flow.py b/tests/components/coolmaster/test_config_flow.py index c71f308dece..81219c41ff8 100644 --- a/tests/components/coolmaster/test_config_flow.py +++ b/tests/components/coolmaster/test_config_flow.py @@ -1,11 +1,9 @@ """Test the Coolmaster config flow.""" -from unittest.mock import patch +from asynctest import patch from homeassistant import config_entries, setup from homeassistant.components.coolmaster.const import AVAILABLE_MODES, DOMAIN -from tests.common import mock_coro - def _flow_data(): options = {"host": "1.1.1.1"} @@ -27,10 +25,9 @@ async def test_form(hass): "homeassistant.components.coolmaster.config_flow.CoolMasterNet.devices", return_value=[1], ), patch( - "homeassistant.components.coolmaster.async_setup", return_value=mock_coro(True) + "homeassistant.components.coolmaster.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.coolmaster.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.coolmaster.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], _flow_data() diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 8ecc341d4d2..6f384faa15c 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -3,9 +3,8 @@ from datetime import datetime, timedelta import json import logging import os -from unittest.mock import Mock, call -from asynctest import patch +from asynctest import Mock, call, patch import pytest from homeassistant.components import zone diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 68e787e1ba0..4d8fadf0654 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -23,7 +23,13 @@ def mock_controller_connect(): """Mock a successful connection.""" with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: service_mock.return_value.open = CoroutineMock() - service_mock.return_value.system.get_config = CoroutineMock() + service_mock.return_value.system.get_config = CoroutineMock( + return_value={ + "mac": "abcd", + "model_info": {"pretty_name": "Pretty Model"}, + "firmware_version": "123", + } + ) service_mock.return_value.lan.get_hosts_list = CoroutineMock() service_mock.return_value.connection.get_status = CoroutineMock() service_mock.return_value.close = CoroutineMock() diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 2751062dedf..26caec65b40 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,7 +1,7 @@ """The tests for the hassio component.""" import os -from unittest.mock import Mock, patch +from asynctest import patch import pytest from homeassistant.auth.const import GROUP_ID_ADMIN @@ -9,8 +9,6 @@ from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY from homeassistant.setup import async_setup_component -from tests.common import mock_coro - MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} @@ -193,8 +191,7 @@ async def test_fail_setup_without_environ_var(hass): async def test_warn_when_cannot_connect(hass, caplog): """Fail warn when we cannot connect.""" with patch.dict(os.environ, MOCK_ENVIRON), patch( - "homeassistant.components.hassio.HassIO.is_connected", - Mock(return_value=mock_coro(None)), + "homeassistant.components.hassio.HassIO.is_connected", return_value=None, ): result = await async_setup_component(hass, "hassio", {}) assert result @@ -311,7 +308,7 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock): assert aioclient_mock.call_count == 4 with patch( - "homeassistant.config.async_check_ha_config_file", return_value=mock_coro() + "homeassistant.config.async_check_ha_config_file", return_value=None ) as mock_check_config: await hass.services.async_call("homeassistant", "restart") await hass.async_block_till_done() diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index 638fbe8f943..f2af78fe160 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -1,24 +1,21 @@ """Tests for the Hisense AEH-W4A1 init file.""" -from unittest.mock import patch - +from asynctest import patch from pyaehw4a1 import exceptions from homeassistant import config_entries, data_entry_flow from homeassistant.components import hisense_aehw4a1 from homeassistant.setup import async_setup_component -from tests.common import mock_coro - async def test_creating_entry_sets_up_climate_discovery(hass): """Test setting up Hisense AEH-W4A1 loads the climate component.""" with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.discovery", - return_value=mock_coro(["1.2.3.4"]), + return_value=["1.2.3.4"], ): with patch( "homeassistant.components.hisense_aehw4a1.climate.async_setup_entry", - return_value=mock_coro(True), + return_value=True, ) as mock_setup: result = await hass.config_entries.flow.async_init( hisense_aehw4a1.DOMAIN, context={"source": config_entries.SOURCE_USER} @@ -41,11 +38,11 @@ async def test_configuring_hisense_w4a1_create_entry(hass): """Test that specifying config will create an entry.""" with patch( "homeassistant.components.hisense_aehw4a1.config_flow.AehW4a1.check", - return_value=mock_coro(True), + return_value=True, ): with patch( "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=mock_coro(True), + return_value=True, ) as mock_setup: await async_setup_component( hass, @@ -65,7 +62,7 @@ async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found(h ): with patch( "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=mock_coro(True), + return_value=True, ) as mock_setup: await async_setup_component( hass, @@ -80,8 +77,7 @@ async def test_configuring_hisense_w4a1_not_creates_entry_for_device_not_found(h async def test_configuring_hisense_w4a1_not_creates_entry_for_empty_import(hass): """Test that specifying config will not create an entry.""" with patch( - "homeassistant.components.hisense_aehw4a1.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.hisense_aehw4a1.async_setup_entry", return_value=True, ) as mock_setup: await async_setup_component(hass, hisense_aehw4a1.DOMAIN, {}) await hass.async_block_till_done() diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index 38a76b7c3fb..fddd149942e 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -2,8 +2,8 @@ # pylint: disable=protected-access import asyncio import unittest -from unittest.mock import Mock, patch +from asynctest import Mock, patch import pytest import voluptuous as vol import yaml @@ -37,7 +37,6 @@ from tests.common import ( async_capture_events, async_mock_service, get_test_home_assistant, - mock_coro, mock_service, patch_yaml_files, ) @@ -215,15 +214,15 @@ class TestComponentsCore(unittest.TestCase): assert mock_error.called assert mock_process.called is False - @patch("homeassistant.core.HomeAssistant.async_stop", return_value=mock_coro()) + @patch("homeassistant.core.HomeAssistant.async_stop", return_value=None) def test_stop_homeassistant(self, mock_stop): """Test stop service.""" stop(self.hass) self.hass.block_till_done() assert mock_stop.called - @patch("homeassistant.core.HomeAssistant.async_stop", return_value=mock_coro()) - @patch("homeassistant.config.async_check_ha_config_file", return_value=mock_coro()) + @patch("homeassistant.core.HomeAssistant.async_stop", return_value=None) + @patch("homeassistant.config.async_check_ha_config_file", return_value=None) def test_restart_homeassistant(self, mock_check, mock_restart): """Test stop service.""" restart(self.hass) @@ -231,7 +230,7 @@ class TestComponentsCore(unittest.TestCase): assert mock_restart.called assert mock_check.called - @patch("homeassistant.core.HomeAssistant.async_stop", return_value=mock_coro()) + @patch("homeassistant.core.HomeAssistant.async_stop", return_value=None) @patch( "homeassistant.config.async_check_ha_config_file", side_effect=HomeAssistantError("Test error"), @@ -243,8 +242,8 @@ class TestComponentsCore(unittest.TestCase): assert mock_check.called assert not mock_restart.called - @patch("homeassistant.core.HomeAssistant.async_stop", return_value=mock_coro()) - @patch("homeassistant.config.async_check_ha_config_file", return_value=mock_coro()) + @patch("homeassistant.core.HomeAssistant.async_stop", return_value=None) + @patch("homeassistant.config.async_check_ha_config_file", return_value=None) def test_check_config(self, mock_check, mock_stop): """Test stop service.""" check_config(self.hass) @@ -271,8 +270,7 @@ async def test_turn_on_to_not_block_for_domains_without_service(hass): service = hass.services._services["homeassistant"]["turn_on"] with patch( - "homeassistant.core.ServiceRegistry.async_call", - side_effect=lambda *args: mock_coro(), + "homeassistant.core.ServiceRegistry.async_call", return_value=None, ) as mock_call: await service.func(service_call) @@ -296,8 +294,7 @@ async def test_entity_update(hass): await async_setup_component(hass, "homeassistant", {}) with patch( - "homeassistant.helpers.entity_component.async_update_entity", - return_value=mock_coro(), + "homeassistant.helpers.entity_component.async_update_entity", return_value=None, ) as mock_update: await hass.services.async_call( "homeassistant", diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index 7a6e1160f24..b3d398377f0 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -1,6 +1,7 @@ """The tests for the IPMA weather component.""" from collections import namedtuple -from unittest.mock import patch + +from asynctest import patch from homeassistant.components import weather from homeassistant.components.weather import ( @@ -22,7 +23,7 @@ from homeassistant.components.weather import ( from homeassistant.setup import async_setup_component from homeassistant.util.dt import now -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry TEST_CONFIG = {"name": "HomeTown", "latitude": "40.00", "longitude": "-8.00"} @@ -128,7 +129,7 @@ async def test_setup_configuration(hass): """Test for successfully setting up the IPMA platform.""" with patch( "homeassistant.components.ipma.weather.async_get_location", - return_value=mock_coro(MockLocation()), + return_value=MockLocation(), ): assert await async_setup_component( hass, @@ -153,7 +154,7 @@ async def test_setup_config_flow(hass): """Test for successfully setting up the IPMA platform.""" with patch( "homeassistant.components.ipma.weather.async_get_location", - return_value=mock_coro(MockLocation()), + return_value=MockLocation(), ): entry = MockConfigEntry(domain="ipma", data=TEST_CONFIG) await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN) @@ -175,7 +176,7 @@ async def test_daily_forecast(hass): """Test for successfully getting daily forecast.""" with patch( "homeassistant.components.ipma.weather.async_get_location", - return_value=mock_coro(MockLocation()), + return_value=MockLocation(), ): assert await async_setup_component( hass, @@ -201,7 +202,7 @@ async def test_hourly_forecast(hass): """Test for successfully getting daily forecast.""" with patch( "homeassistant.components.ipma.weather.async_get_location", - return_value=mock_coro(MockLocation()), + return_value=MockLocation(), ): assert await async_setup_component( hass, diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index 5deafeb08a7..942d95cc503 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -1,14 +1,11 @@ """Tests for iZone.""" -from unittest.mock import Mock, patch - +from asynctest import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components.izone.const import DISPATCH_CONTROLLER_DISCOVERED, IZONE -from tests.common import mock_coro - @pytest.fixture def mock_disco(): @@ -24,7 +21,7 @@ def _mock_start_discovery(hass, mock_disco): def do_disovered(*args): async_dispatcher_send(hass, DISPATCH_CONTROLLER_DISCOVERED, True) - return mock_coro(mock_disco) + return mock_disco return do_disovered @@ -36,7 +33,7 @@ async def test_not_found(hass, mock_disco): "homeassistant.components.izone.config_flow.async_start_discovery_service" ) as start_disco, patch( "homeassistant.components.izone.config_flow.async_stop_discovery_service", - return_value=mock_coro(), + return_value=None, ) as stop_disco: start_disco.side_effect = _mock_start_discovery(hass, mock_disco) result = await hass.config_entries.flow.async_init( @@ -59,13 +56,12 @@ async def test_found(hass, mock_disco): mock_disco.pi_disco.controllers["blah"] = object() with patch( - "homeassistant.components.izone.climate.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.izone.climate.async_setup_entry", return_value=True, ) as mock_setup, patch( "homeassistant.components.izone.config_flow.async_start_discovery_service" ) as start_disco, patch( "homeassistant.components.izone.async_start_discovery_service", - return_value=mock_coro(), + return_value=None, ): start_disco.side_effect = _mock_start_discovery(hass, mock_disco) result = await hass.config_entries.flow.async_init( diff --git a/tests/components/luftdaten/test_config_flow.py b/tests/components/luftdaten/test_config_flow.py index 8718db88ce1..e0e54a6b790 100644 --- a/tests/components/luftdaten/test_config_flow.py +++ b/tests/components/luftdaten/test_config_flow.py @@ -1,13 +1,14 @@ """Define tests for the Luftdaten config flow.""" from datetime import timedelta -from unittest.mock import patch + +from asynctest import patch from homeassistant import data_entry_flow from homeassistant.components.luftdaten import DOMAIN, config_flow from homeassistant.components.luftdaten.const import CONF_SENSOR_ID from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry async def test_duplicate_error(hass): @@ -29,7 +30,7 @@ async def test_communication_error(hass): flow = config_flow.LuftDatenFlowHandler() flow.hass = hass - with patch("luftdaten.Luftdaten.get_data", return_value=mock_coro(None)): + with patch("luftdaten.Luftdaten.get_data", return_value=None): result = await flow.async_step_user(user_input=conf) assert result["errors"] == {CONF_SENSOR_ID: "invalid_sensor"} @@ -41,8 +42,8 @@ async def test_invalid_sensor(hass): flow = config_flow.LuftDatenFlowHandler() flow.hass = hass - with patch("luftdaten.Luftdaten.get_data", return_value=mock_coro(False)), patch( - "luftdaten.Luftdaten.validate_sensor", return_value=mock_coro(False) + with patch("luftdaten.Luftdaten.get_data", return_value=False), patch( + "luftdaten.Luftdaten.validate_sensor", return_value=False ): result = await flow.async_step_user(user_input=conf) assert result["errors"] == {CONF_SENSOR_ID: "invalid_sensor"} @@ -66,8 +67,8 @@ async def test_step_import(hass): flow = config_flow.LuftDatenFlowHandler() flow.hass = hass - with patch("luftdaten.Luftdaten.get_data", return_value=mock_coro(True)), patch( - "luftdaten.Luftdaten.validate_sensor", return_value=mock_coro(True) + with patch("luftdaten.Luftdaten.get_data", return_value=True), patch( + "luftdaten.Luftdaten.validate_sensor", return_value=True ): result = await flow.async_step_import(import_config=conf) @@ -91,8 +92,8 @@ async def test_step_user(hass): flow = config_flow.LuftDatenFlowHandler() flow.hass = hass - with patch("luftdaten.Luftdaten.get_data", return_value=mock_coro(True)), patch( - "luftdaten.Luftdaten.validate_sensor", return_value=mock_coro(True) + with patch("luftdaten.Luftdaten.get_data", return_value=True), patch( + "luftdaten.Luftdaten.validate_sensor", return_value=True ): result = await flow.async_step_user(user_input=conf) diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index 803ca006965..478a3fb29bd 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -1,6 +1,7 @@ """The tests for the microsoft face platform.""" import asyncio -from unittest.mock import patch + +from asynctest import patch from homeassistant.components import camera, microsoft_face as mf from homeassistant.components.microsoft_face import ( @@ -18,12 +19,7 @@ from homeassistant.components.microsoft_face import ( from homeassistant.const import ATTR_NAME from homeassistant.setup import setup_component -from tests.common import ( - assert_setup_component, - get_test_home_assistant, - load_fixture, - mock_coro, -) +from tests.common import assert_setup_component, get_test_home_assistant, load_fixture def create_group(hass, name): @@ -97,7 +93,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_setup_component(self, mock_update): """Set up component.""" @@ -106,7 +102,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_setup_component_wrong_api_key(self, mock_update): """Set up component without api key.""" @@ -115,7 +111,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_setup_component_test_service(self, mock_update): """Set up component.""" @@ -171,7 +167,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_service_groups(self, mock_update, aioclient_mock): """Set up component, test groups services.""" @@ -258,7 +254,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_service_train(self, mock_update, aioclient_mock): """Set up component, test train groups services.""" @@ -278,7 +274,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.camera.async_get_image", - return_value=mock_coro(camera.Image("image/jpeg", b"Test")), + return_value=camera.Image("image/jpeg", b"Test"), ) def test_service_face(self, camera_mock, aioclient_mock): """Set up component, test person face services.""" @@ -318,7 +314,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_service_status_400(self, mock_update, aioclient_mock): """Set up component, test groups services with error.""" @@ -340,7 +336,7 @@ class TestMicrosoftFaceSetup: @patch( "homeassistant.components.microsoft_face.MicrosoftFace.update_store", - return_value=mock_coro(), + return_value=None, ) def test_service_status_timeout(self, mock_update, aioclient_mock): """Set up component, test groups services with timeout.""" diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index ea7e22239b2..1a634916781 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -1,12 +1,12 @@ """Test Mikrotik setup process.""" -from unittest.mock import Mock, patch +from asynctest import CoroutineMock, Mock, patch from homeassistant.components import mikrotik from homeassistant.setup import async_setup_component from . import MOCK_DATA -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry async def test_setup_with_no_config(hass): @@ -23,9 +23,9 @@ async def test_successful_config_entry(hass): with patch.object(mikrotik, "MikrotikHub") as mock_hub, patch( "homeassistant.helpers.device_registry.async_get_registry", - return_value=mock_coro(mock_registry), + return_value=mock_registry, ): - mock_hub.return_value.async_setup.return_value = mock_coro(True) + mock_hub.return_value.async_setup = CoroutineMock(return_value=True) mock_hub.return_value.serial_num = "12345678" mock_hub.return_value.model = "RB750" mock_hub.return_value.hostname = "mikrotik" @@ -55,7 +55,7 @@ async def test_hub_fail_setup(hass): entry.add_to_hass(hass) with patch.object(mikrotik, "MikrotikHub") as mock_hub: - mock_hub.return_value.async_setup.return_value = mock_coro(False) + mock_hub.return_value.async_setup = CoroutineMock(return_value=False) assert await mikrotik.async_setup_entry(hass, entry) is False assert mikrotik.DOMAIN not in hass.data @@ -67,10 +67,9 @@ async def test_unload_entry(hass): entry.add_to_hass(hass) with patch.object(mikrotik, "MikrotikHub") as mock_hub, patch( - "homeassistant.helpers.device_registry.async_get_registry", - return_value=mock_coro(Mock()), + "homeassistant.helpers.device_registry.async_get_registry", return_value=Mock(), ): - mock_hub.return_value.async_setup.return_value = mock_coro(True) + mock_hub.return_value.async_setup = CoroutineMock(return_value=True) mock_hub.return_value.serial_num = "12345678" mock_hub.return_value.model = "RB750" mock_hub.return_value.hostname = "mikrotik" diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 290b70953af..ca5a89a6e63 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -3,8 +3,8 @@ from datetime import datetime, timedelta import json import ssl import unittest -from unittest import mock +from asynctest import CoroutineMock, MagicMock, call, mock_open, patch import pytest import voluptuous as vol @@ -31,7 +31,6 @@ from tests.common import ( async_mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, - mock_coro, mock_device_registry, mock_mqtt_component, mock_registry, @@ -56,9 +55,9 @@ def entity_reg(hass): @pytest.fixture def mock_mqtt(): """Make sure connection is established.""" - with mock.patch("homeassistant.components.mqtt.MQTT") as mock_mqtt: - mock_mqtt.return_value.async_connect.return_value = mock_coro(True) - mock_mqtt.return_value.async_disconnect.return_value = mock_coro(True) + with patch("homeassistant.components.mqtt.MQTT") as mock_mqtt: + mock_mqtt.return_value.async_connect = CoroutineMock(return_value=True) + mock_mqtt.return_value.async_disconnect = CoroutineMock(return_value=True) yield mock_mqtt @@ -67,7 +66,7 @@ async def async_mock_mqtt_client(hass, config=None): if config is None: config = {mqtt.CONF_BROKER: "mock-broker"} - with mock.patch("paho.mqtt.client.Client") as mock_client: + with patch("paho.mqtt.client.Client") as mock_client: mock_client().connect.return_value = 0 mock_client().subscribe.return_value = (0, 0) mock_client().unsubscribe.return_value = (0, 0) @@ -583,12 +582,12 @@ class TestMQTTCallbacks(unittest.TestCase): # Fake that the client is connected self.hass.data["mqtt"].connected = True - calls_a = mock.MagicMock() + calls_a = MagicMock() mqtt.subscribe(self.hass, "test/state", calls_a) self.hass.block_till_done() assert calls_a.called - calls_b = mock.MagicMock() + calls_b = MagicMock() mqtt.subscribe(self.hass, "test/state", calls_b) self.hass.block_till_done() assert calls_b.called @@ -639,9 +638,9 @@ class TestMQTTCallbacks(unittest.TestCase): self.hass.block_till_done() expected = [ - mock.call("test/state", 2), - mock.call("test/state", 0), - mock.call("test/state", 1), + call("test/state", 2), + call("test/state", 0), + call("test/state", 1), ] assert self.hass.data["mqtt"]._mqttc.subscribe.mock_calls == expected @@ -653,7 +652,7 @@ class TestMQTTCallbacks(unittest.TestCase): self.hass.data["mqtt"]._mqtt_on_connect(None, None, None, 0) self.hass.block_till_done() - expected.append(mock.call("test/state", 1)) + expected.append(call("test/state", 1)) assert self.hass.data["mqtt"]._mqttc.subscribe.mock_calls == expected @@ -661,9 +660,9 @@ async def test_setup_embedded_starts_with_no_config(hass): """Test setting up embedded server with no config.""" client_config = ("localhost", 1883, "user", "pass", None, "3.1.1") - with mock.patch( + with patch( "homeassistant.components.mqtt.server.async_start", - return_value=mock_coro(return_value=(True, client_config)), + return_value=(True, client_config), ) as _start: await async_mock_mqtt_client(hass, {}) assert _start.call_count == 1 @@ -673,11 +672,10 @@ async def test_setup_embedded_with_embedded(hass): """Test setting up embedded server with no config.""" client_config = ("localhost", 1883, "user", "pass", None, "3.1.1") - with mock.patch( + with patch( "homeassistant.components.mqtt.server.async_start", - return_value=mock_coro(return_value=(True, client_config)), + return_value=(True, client_config), ) as _start: - _start.return_value = mock_coro(return_value=(True, client_config)) await async_mock_mqtt_client(hass, {"embedded": None}) assert _start.call_count == 1 @@ -686,7 +684,7 @@ async def test_setup_fails_if_no_connect_broker(hass): """Test for setup failure if connection to broker is missing.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) - with mock.patch("paho.mqtt.client.Client") as mock_client: + with patch("paho.mqtt.client.Client") as mock_client: mock_client().connect = lambda *args: 1 assert not await mqtt.async_setup_entry(hass, entry) @@ -695,8 +693,8 @@ async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass): """Test for setup failure if connection to broker is missing.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) - with mock.patch("paho.mqtt.client.Client") as mock_client: - mock_client().connect = mock.Mock(side_effect=OSError("Connection error")) + with patch("paho.mqtt.client.Client") as mock_client: + mock_client().connect = MagicMock(side_effect=OSError("Connection error")) with pytest.raises(ConfigEntryNotReady): await mqtt.async_setup_entry(hass, entry) @@ -808,7 +806,7 @@ async def test_mqtt_subscribes_topics_on_connect(hass): mqtt.Subscription("still/pending", None, 1), ] - hass.add_job = mock.MagicMock() + hass.add_job = MagicMock() hass.data["mqtt"]._mqtt_on_connect(None, None, 0, 0) await hass.async_block_till_done() @@ -874,7 +872,7 @@ async def test_dump_service(hass): """Test that we can dump a topic.""" await async_mock_mqtt_component(hass) - mock_open = mock.mock_open() + mopen = mock_open() await hass.services.async_call( "mqtt", "dump", {"topic": "bla/#", "duration": 3}, blocking=True @@ -882,11 +880,11 @@ async def test_dump_service(hass): async_fire_mqtt_message(hass, "bla/1", "test1") async_fire_mqtt_message(hass, "bla/2", "test2") - with mock.patch("homeassistant.components.mqtt.open", mock_open): + with patch("homeassistant.components.mqtt.open", mopen): async_fire_time_changed(hass, utcnow() + timedelta(seconds=3)) await hass.async_block_till_done() - writes = mock_open.return_value.write.mock_calls + writes = mopen.return_value.write.mock_calls assert len(writes) == 2 assert writes[0][1][0] == "bla/1,test1\n" assert writes[1][1][0] == "bla/2,test2\n" @@ -1251,7 +1249,7 @@ async def test_debug_info_wildcard(hass, mqtt_mock): ] start_dt = datetime(2019, 1, 1, 0, 0, 0) - with mock.patch("homeassistant.util.dt.utcnow") as dt_utcnow: + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: dt_utcnow.return_value = start_dt async_fire_mqtt_message(hass, "sensor/abc", "123") @@ -1293,7 +1291,7 @@ async def test_debug_info_filter_same(hass, mqtt_mock): dt1 = datetime(2019, 1, 1, 0, 0, 0) dt2 = datetime(2019, 1, 1, 0, 0, 1) - with mock.patch("homeassistant.util.dt.utcnow") as dt_utcnow: + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: dt_utcnow.return_value = dt1 async_fire_mqtt_message(hass, "sensor/abc", "123") async_fire_mqtt_message(hass, "sensor/abc", "123") diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 45473d6f448..205e7400eb3 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -153,8 +153,7 @@ light: payload_off: "off" """ -from unittest import mock -from unittest.mock import patch +from asynctest import call, patch from homeassistant.components import light, mqtt from homeassistant.components.mqtt.discovery import async_start @@ -188,7 +187,6 @@ from tests.common import ( MockConfigEntry, assert_setup_component, async_fire_mqtt_message, - mock_coro, ) from tests.components.light import common @@ -673,7 +671,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ) with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", - return_value=mock_coro(fake_state), + return_value=fake_state, ): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, config) @@ -716,12 +714,12 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light_rgb/set", "on", 2, False), - mock.call("test_light_rgb/rgb/set", "255,128,0", 2, False), - mock.call("test_light_rgb/brightness/set", 50, 2, False), - mock.call("test_light_rgb/hs/set", "359.0,78.0", 2, False), - mock.call("test_light_rgb/white_value/set", 80, 2, False), - mock.call("test_light_rgb/xy/set", "0.14,0.131", 2, False), + call("test_light_rgb/set", "on", 2, False), + call("test_light_rgb/rgb/set", "255,128,0", 2, False), + call("test_light_rgb/brightness/set", 50, 2, False), + call("test_light_rgb/hs/set", "359.0,78.0", 2, False), + call("test_light_rgb/white_value/set", 80, 2, False), + call("test_light_rgb/xy/set", "0.14,0.131", 2, False), ], any_order=True, ) @@ -760,8 +758,8 @@ async def test_sending_mqtt_rgb_command_with_template(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light_rgb/set", "on", 0, False), - mock.call("test_light_rgb/rgb/set", "#ff803f", 0, False), + call("test_light_rgb/set", "on", 0, False), + call("test_light_rgb/rgb/set", "#ff803f", 0, False), ], any_order=True, ) @@ -795,8 +793,8 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light_color_temp/set", "on", 0, False), - mock.call("test_light_color_temp/color_temp/set", "10", 0, False), + call("test_light_color_temp/set", "on", 0, False), + call("test_light_color_temp/color_temp/set", "10", 0, False), ], any_order=True, ) @@ -980,8 +978,8 @@ async def test_on_command_first(hass, mqtt_mock): # test_light/bright: 50 mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/set", "ON", 0, False), - mock.call("test_light/bright", 50, 0, False), + call("test_light/set", "ON", 0, False), + call("test_light/bright", 50, 0, False), ], any_order=True, ) @@ -1015,8 +1013,8 @@ async def test_on_command_last(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/bright", 50, 0, False), - mock.call("test_light/set", "ON", 0, False), + call("test_light/bright", 50, 0, False), + call("test_light/set", "ON", 0, False), ], any_order=True, ) @@ -1072,8 +1070,8 @@ async def test_on_command_brightness(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/rgb", "255,128,0", 0, False), - mock.call("test_light/bright", 50, 0, False), + call("test_light/rgb", "255,128,0", 0, False), + call("test_light/bright", 50, 0, False), ], any_order=True, ) @@ -1102,8 +1100,8 @@ async def test_on_command_rgb(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/rgb", "127,127,127", 0, False), - mock.call("test_light/set", "ON", 0, False), + call("test_light/rgb", "127,127,127", 0, False), + call("test_light/set", "ON", 0, False), ], any_order=True, ) @@ -1138,8 +1136,8 @@ async def test_on_command_rgb_template(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/rgb", "127/127/127", 0, False), - mock.call("test_light/set", "ON", 0, False), + call("test_light/rgb", "127/127/127", 0, False), + call("test_light/set", "ON", 0, False), ], any_order=True, ) @@ -1174,8 +1172,8 @@ async def test_effect(hass, mqtt_mock): # test_light/set: 'ON' mqtt_mock.async_publish.assert_has_calls( [ - mock.call("test_light/effect/set", "rainbow", 0, False), - mock.call("test_light/set", "ON", 0, False), + call("test_light/effect/set", "rainbow", 0, False), + call("test_light/set", "ON", 0, False), ], any_order=True, ) diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 824085ea833..1e3ac34af89 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -88,8 +88,8 @@ light: brightness_scale: 99 """ import json -from unittest import mock -from unittest.mock import patch + +from asynctest import call, patch from homeassistant.components import light from homeassistant.const import ( @@ -123,7 +123,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) -from tests.common import async_fire_mqtt_message, mock_coro +from tests.common import async_fire_mqtt_message from tests.components.light import common DEFAULT_CONFIG = { @@ -323,7 +323,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", - return_value=mock_coro(fake_state), + return_value=fake_state, ): assert await async_setup_component( hass, @@ -397,7 +397,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 0, "g": 123, "b": 255,' @@ -407,7 +407,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): 2, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59,' @@ -417,7 +417,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): 2, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0,' @@ -471,7 +471,7 @@ async def test_sending_hs_color(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"h": 210.824, "s": 100.0},' @@ -480,7 +480,7 @@ async def test_sending_hs_color(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"h": 359.0, "s": 78.0},' @@ -489,7 +489,7 @@ async def test_sending_hs_color(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"h": 30.118, "s": 100.0},' @@ -532,19 +532,19 @@ async def test_sending_rgb_color_no_brightness(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call( + call( "test_light_rgb/set", JsonValidator('{"state": "ON", "color": {"r": 0, "g": 24, "b": 50}}'), 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator('{"state": "ON", "color": {"r": 50, "g": 11, "b": 11}}'), 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator('{"state": "ON", "color": {"r": 255, "g": 128, "b": 0}}'), 0, @@ -585,7 +585,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 0, "g": 123, "b": 255},' @@ -594,7 +594,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59},' @@ -603,7 +603,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0},' @@ -647,7 +647,7 @@ async def test_sending_xy_color(hass, mqtt_mock): mqtt_mock.async_publish.assert_has_calls( [ - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"x": 0.14, "y": 0.131},' @@ -656,7 +656,7 @@ async def test_sending_xy_color(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"x": 0.654, "y": 0.301},' @@ -665,7 +665,7 @@ async def test_sending_xy_color(hass, mqtt_mock): 0, False, ), - mock.call( + call( "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"x": 0.611, "y": 0.375},' diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 37617192dd5..20b5ecefd89 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -26,7 +26,7 @@ If your light doesn't support white value feature, omit `white_value_template`. If your light doesn't support RGB feature, omit `(red|green|blue)_template`. """ -from unittest.mock import patch +from asynctest import patch from homeassistant.components import light from homeassistant.const import ( @@ -60,7 +60,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) -from tests.common import assert_setup_component, async_fire_mqtt_message, mock_coro +from tests.common import assert_setup_component, async_fire_mqtt_message from tests.components.light import common DEFAULT_CONFIG = { @@ -287,7 +287,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", - return_value=mock_coro(fake_state), + return_value=fake_state, ): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component( diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index 60ca4c07fb5..7d3ddb1cf4b 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -1,7 +1,6 @@ """Define tests for the Notion config flow.""" -from unittest.mock import patch - import aionotion +from asynctest import patch import pytest from homeassistant import data_entry_flow diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index 4aec9e68709..f5a246bcb4d 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -1,6 +1,7 @@ """The tests for the openalpr cloud platform.""" import asyncio -from unittest.mock import PropertyMock, patch + +from asynctest import PropertyMock, patch from homeassistant.components import camera, image_processing as ip from homeassistant.components.openalpr_cloud.image_processing import OPENALPR_API_URL diff --git a/tests/components/openalpr_local/test_image_processing.py b/tests/components/openalpr_local/test_image_processing.py index f28ee6f02d4..3f62c906974 100644 --- a/tests/components/openalpr_local/test_image_processing.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -1,5 +1,5 @@ """The tests for the openalpr local platform.""" -from unittest.mock import MagicMock, PropertyMock, patch +from asynctest import MagicMock, PropertyMock, patch import homeassistant.components.image_processing as ip from homeassistant.const import ATTR_ENTITY_PICTURE diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index f57ad20f5d5..2dec360eca0 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Opentherm Gateway config flow.""" import asyncio -from unittest.mock import patch +from asynctest import patch from pyotgw.vars import OTGW_ABOUT from serial import SerialException diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index ae999afe305..514a559ac1d 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for OwnTracks config flow.""" -from unittest.mock import Mock, patch - +from asynctest import Mock, patch import pytest from homeassistant import data_entry_flow diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index fca0f624a29..4351470cff5 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -1,6 +1,5 @@ """Define tests for the OpenUV config flow.""" -from unittest.mock import patch - +from asynctest import patch from regenmaschine.errors import RainMachineError from homeassistant import data_entry_flow diff --git a/tests/components/ring/test_config_flow.py b/tests/components/ring/test_config_flow.py index 5712106333f..421e8a26694 100644 --- a/tests/components/ring/test_config_flow.py +++ b/tests/components/ring/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Ring config flow.""" -from unittest.mock import Mock, patch +from asynctest import Mock, patch from homeassistant import config_entries, setup from homeassistant.components.ring import DOMAIN diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index b34ba3d1229..58c9192de7d 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -1,11 +1,10 @@ """The tests for the rmvtransport platform.""" import datetime -from unittest.mock import patch + +from asynctest import patch from homeassistant.setup import async_setup_component -from tests.common import mock_coro - VALID_CONFIG_MINIMAL = { "sensor": {"platform": "rmvtransport", "next_departure": [{"station": "3000010"}]} } @@ -163,8 +162,7 @@ def get_no_departures_mock(): async def test_rmvtransport_min_config(hass): """Test minimal rmvtransport configuration.""" with patch( - "RMVtransport.RMVtransport.get_departures", - return_value=mock_coro(get_departures_mock()), + "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) is True @@ -183,8 +181,7 @@ async def test_rmvtransport_min_config(hass): async def test_rmvtransport_name_config(hass): """Test custom name configuration.""" with patch( - "RMVtransport.RMVtransport.get_departures", - return_value=mock_coro(get_departures_mock()), + "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_NAME) @@ -195,8 +192,7 @@ async def test_rmvtransport_name_config(hass): async def test_rmvtransport_misc_config(hass): """Test misc configuration.""" with patch( - "RMVtransport.RMVtransport.get_departures", - return_value=mock_coro(get_departures_mock()), + "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MISC) @@ -208,8 +204,7 @@ async def test_rmvtransport_misc_config(hass): async def test_rmvtransport_dest_config(hass): """Test destination configuration.""" with patch( - "RMVtransport.RMVtransport.get_departures", - return_value=mock_coro(get_departures_mock()), + "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_DEST) @@ -227,7 +222,7 @@ async def test_rmvtransport_no_departures(hass): """Test for no departures.""" with patch( "RMVtransport.RMVtransport.get_departures", - return_value=mock_coro(get_no_departures_mock()), + return_value=get_no_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) diff --git a/tests/components/sentry/test_config_flow.py b/tests/components/sentry/test_config_flow.py index 7ce34c13f53..22c4367ddf2 100644 --- a/tests/components/sentry/test_config_flow.py +++ b/tests/components/sentry/test_config_flow.py @@ -1,6 +1,5 @@ """Test the sentry config flow.""" -from unittest.mock import patch - +from asynctest import patch from sentry_sdk.utils import BadDsn from homeassistant import config_entries, setup diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index a5e8cb1d946..156741d8c9b 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -4,7 +4,8 @@ import os import tempfile from typing import Tuple import unittest -from unittest.mock import Mock, patch + +from asynctest import Mock, patch from homeassistant.components import shell_command from homeassistant.setup import setup_component diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index b983f8af487..6065c323b91 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for SMHI config flow.""" -from unittest.mock import Mock, patch - +from asynctest import Mock, patch from smhi.smhi_lib import Smhi as SmhiApi, SmhiForecastException from homeassistant.components.smhi import config_flow diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 3485a108d5b..357b670a38a 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -2,7 +2,8 @@ import asyncio from datetime import datetime import logging -from unittest.mock import Mock, patch + +from asynctest import Mock, patch from homeassistant.components.smhi import weather as weather_smhi from homeassistant.components.smhi.const import ATTR_SMHI_CLOUDINESS diff --git a/tests/components/solarlog/test_config_flow.py b/tests/components/solarlog/test_config_flow.py index 7828290560a..4d8c11074db 100644 --- a/tests/components/solarlog/test_config_flow.py +++ b/tests/components/solarlog/test_config_flow.py @@ -1,6 +1,5 @@ """Test the solarlog config flow.""" -from unittest.mock import patch - +from asynctest import patch import pytest from homeassistant import config_entries, data_entry_flow, setup diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 8698fddbeab..24cdb375fde 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -1,6 +1,5 @@ """Test the Tesla config flow.""" -from unittest.mock import patch - +from asynctest import patch from teslajsonpy import TeslaException from homeassistant import config_entries, data_entry_flow, setup diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index d835c06f256..bb1a0e7cc64 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -1,10 +1,7 @@ """Common tradfri test fixtures.""" -from unittest.mock import patch - +from asynctest import patch import pytest -from tests.common import mock_coro - @pytest.fixture def mock_gateway_info(): @@ -19,5 +16,5 @@ def mock_gateway_info(): def mock_entry_setup(): """Mock entry setup.""" with patch("homeassistant.components.tradfri.async_setup_entry") as mock_setup: - mock_setup.return_value = mock_coro(True) + mock_setup.return_value = True yield mock_setup diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 18fb55eda2f..2a4a831575a 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -1,12 +1,11 @@ """Test the Tradfri config flow.""" -from unittest.mock import patch - +from asynctest import patch import pytest from homeassistant import data_entry_flow from homeassistant.components.tradfri import config_flow -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry @pytest.fixture @@ -20,9 +19,7 @@ def mock_auth(): async def test_user_connection_successful(hass, mock_auth, mock_entry_setup): """Test a successful connection.""" - mock_auth.side_effect = lambda hass, host, code: mock_coro( - {"host": host, "gateway_id": "bla"} - ) + mock_auth.side_effect = lambda hass, host, code: {"host": host, "gateway_id": "bla"} flow = await hass.config_entries.flow.async_init( "tradfri", context={"source": "user"} @@ -80,9 +77,7 @@ async def test_user_connection_bad_key(hass, mock_auth, mock_entry_setup): async def test_discovery_connection(hass, mock_auth, mock_entry_setup): """Test a connection via discovery.""" - mock_auth.side_effect = lambda hass, host, code: mock_coro( - {"host": host, "gateway_id": "bla"} - ) + mock_auth.side_effect = lambda hass, host, code: {"host": host, "gateway_id": "bla"} flow = await hass.config_entries.flow.async_init( "tradfri", context={"source": "zeroconf"}, data={"host": "123.123.123.123"} @@ -104,9 +99,12 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup): async def test_import_connection(hass, mock_auth, mock_entry_setup): """Test a connection via import.""" - mock_auth.side_effect = lambda hass, host, code: mock_coro( - {"host": host, "gateway_id": "bla", "identity": "mock-iden", "key": "mock-key"} - ) + mock_auth.side_effect = lambda hass, host, code: { + "host": host, + "gateway_id": "bla", + "identity": "mock-iden", + "key": "mock-key", + } flow = await hass.config_entries.flow.async_init( "tradfri", @@ -132,9 +130,12 @@ async def test_import_connection(hass, mock_auth, mock_entry_setup): async def test_import_connection_no_groups(hass, mock_auth, mock_entry_setup): """Test a connection via import and no groups allowed.""" - mock_auth.side_effect = lambda hass, host, code: mock_coro( - {"host": host, "gateway_id": "bla", "identity": "mock-iden", "key": "mock-key"} - ) + mock_auth.side_effect = lambda hass, host, code: { + "host": host, + "gateway_id": "bla", + "identity": "mock-iden", + "key": "mock-key", + } flow = await hass.config_entries.flow.async_init( "tradfri", @@ -160,9 +161,12 @@ async def test_import_connection_no_groups(hass, mock_auth, mock_entry_setup): async def test_import_connection_legacy(hass, mock_gateway_info, mock_entry_setup): """Test a connection via import.""" - mock_gateway_info.side_effect = lambda hass, host, identity, key: mock_coro( - {"host": host, "identity": identity, "key": key, "gateway_id": "mock-gateway"} - ) + mock_gateway_info.side_effect = lambda hass, host, identity, key: { + "host": host, + "identity": identity, + "key": key, + "gateway_id": "mock-gateway", + } result = await hass.config_entries.flow.async_init( "tradfri", @@ -187,9 +191,12 @@ async def test_import_connection_legacy_no_groups( hass, mock_gateway_info, mock_entry_setup ): """Test a connection via legacy import and no groups allowed.""" - mock_gateway_info.side_effect = lambda hass, host, identity, key: mock_coro( - {"host": host, "identity": identity, "key": key, "gateway_id": "mock-gateway"} - ) + mock_gateway_info.side_effect = lambda hass, host, identity, key: { + "host": host, + "identity": identity, + "key": key, + "gateway_id": "mock-gateway", + } result = await hass.config_entries.flow.async_init( "tradfri", diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index cf9034df8d6..bdb45ee4306 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -1,9 +1,9 @@ """Tests for Tradfri setup.""" -from unittest.mock import patch +from asynctest import patch from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_coro +from tests.common import MockConfigEntry async def test_config_yaml_host_not_imported(hass): @@ -51,9 +51,12 @@ async def test_config_json_host_not_imported(hass): async def test_config_json_host_imported(hass, mock_gateway_info, mock_entry_setup): """Test that we import a configured host.""" - mock_gateway_info.side_effect = lambda hass, host, identity, key: mock_coro( - {"host": host, "identity": identity, "key": key, "gateway_id": "mock-gateway"} - ) + mock_gateway_info.side_effect = lambda hass, host, identity, key: { + "host": host, + "identity": identity, + "key": key, + "gateway_id": "mock-gateway", + } with patch( "homeassistant.components.tradfri.load_json", diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index a2df00aba2d..464ccc90675 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -1,7 +1,8 @@ """Test UPnP/IGD setup process.""" from ipaddress import IPv4Address -from unittest.mock import patch + +from asynctest import patch from homeassistant.components import upnp from homeassistant.components.upnp.device import Device diff --git a/tests/conftest.py b/tests/conftest.py index 700240bf627..ccc16c8ef82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ """Set up some common test helper things.""" import functools import logging -from unittest.mock import patch +from asynctest import patch import pytest import requests_mock as _requests_mock @@ -26,7 +26,6 @@ from tests.common import ( # noqa: E402, isort:skip INSTANCES, MockUser, async_test_home_assistant, - mock_coro, mock_storage as mock_storage, ) from tests.test_util.aiohttp import mock_aiohttp_client # noqa: E402, isort:skip @@ -128,7 +127,7 @@ def mock_device_tracker_conf(): side_effect=mock_update_config, ), patch( "homeassistant.components.device_tracker.legacy.async_load_config", - side_effect=lambda *args: mock_coro(devices), + side_effect=lambda *args: devices, ): yield devices From c4c2b4f9da8a5af674b4ae2854427da817a45535 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 25 Apr 2020 17:08:37 -0500 Subject: [PATCH 061/511] Add ability to ignore rachio discovery (#34649) * Add ability to ignore rachio discovery * update test * Update tests/components/rachio/test_config_flow.py Co-Authored-By: Chris Talkington * Update tests/components/rachio/test_config_flow.py Co-Authored-By: Chris Talkington Co-authored-by: Chris Talkington --- homeassistant/components/rachio/config_flow.py | 4 ++++ tests/components/rachio/test_config_flow.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rachio/config_flow.py b/homeassistant/components/rachio/config_flow.py index 2f5835ad614..df9ead463f7 100644 --- a/homeassistant/components/rachio/config_flow.py +++ b/homeassistant/components/rachio/config_flow.py @@ -89,6 +89,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # they already have one configured as they can always # add a new one via "+" return self.async_abort(reason="already_configured") + properties = { + key.lower(): value for (key, value) in homekit_info["properties"].items() + } + await self.async_set_unique_id(properties["id"]) return await self.async_step_user() async def async_step_import(self, user_input): diff --git a/tests/components/rachio/test_config_flow.py b/tests/components/rachio/test_config_flow.py index 57575fe5501..cadc346e403 100644 --- a/tests/components/rachio/test_config_flow.py +++ b/tests/components/rachio/test_config_flow.py @@ -111,16 +111,26 @@ async def test_form_homekit(hass): await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "form" assert result["errors"] == {} + flow = next( + flow + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert flow["context"]["unique_id"] == "AA:BB:CC:DD:EE:FF" entry = MockConfigEntry(domain=DOMAIN, data={CONF_API_KEY: "api_key"}) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "abort" assert result["reason"] == "already_configured" From 29383f98b55b11e6a42ccb1476abb83872113f6c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 25 Apr 2020 17:08:53 -0500 Subject: [PATCH 062/511] Add ability to ignore tado discovery (#34650) * Add ability to ignore tado discovery * update test * Update tests/components/tado/test_config_flow.py Co-Authored-By: Chris Talkington * Update tests/components/tado/test_config_flow.py Co-Authored-By: Chris Talkington Co-authored-by: Chris Talkington --- homeassistant/components/tado/config_flow.py | 4 ++++ tests/components/tado/test_config_flow.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tado/config_flow.py b/homeassistant/components/tado/config_flow.py index c14b4284cf3..fb60b820ab9 100644 --- a/homeassistant/components/tado/config_flow.py +++ b/homeassistant/components/tado/config_flow.py @@ -92,6 +92,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # they already have one configured as they can always # add a new one via "+" return self.async_abort(reason="already_configured") + properties = { + key.lower(): value for (key, value) in homekit_info["properties"].items() + } + await self.async_set_unique_id(properties["id"]) return await self.async_step_user() async def async_step_import(self, user_input): diff --git a/tests/components/tado/test_config_flow.py b/tests/components/tado/test_config_flow.py index fb9156d96d9..0c75eadfb0e 100644 --- a/tests/components/tado/test_config_flow.py +++ b/tests/components/tado/test_config_flow.py @@ -151,10 +151,18 @@ async def test_form_homekit(hass): await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "form" assert result["errors"] == {} + flow = next( + flow + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert flow["context"]["unique_id"] == "AA:BB:CC:DD:EE:FF" entry = MockConfigEntry( domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"} @@ -162,6 +170,8 @@ async def test_form_homekit(hass): entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "abort" From fd339be4586939650b215e5af76e74160ca30172 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 25 Apr 2020 17:09:08 -0500 Subject: [PATCH 063/511] Add ability to ignore myq discovery (#34652) * Add ability to ignore myq discovery * update test * Update tests/components/myq/test_config_flow.py Co-Authored-By: Chris Talkington * Update tests/components/myq/test_config_flow.py Co-Authored-By: Chris Talkington * reset ci Co-authored-by: Chris Talkington --- homeassistant/components/myq/config_flow.py | 4 ++++ tests/components/myq/test_config_flow.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/myq/config_flow.py b/homeassistant/components/myq/config_flow.py index 07d57921e35..4fd267f1b21 100644 --- a/homeassistant/components/myq/config_flow.py +++ b/homeassistant/components/myq/config_flow.py @@ -75,6 +75,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # they already have one configured as they can always # add a new one via "+" return self.async_abort(reason="already_configured") + properties = { + key.lower(): value for (key, value) in homekit_info["properties"].items() + } + await self.async_set_unique_id(properties["id"]) return await self.async_step_user() async def async_step_import(self, user_input): diff --git a/tests/components/myq/test_config_flow.py b/tests/components/myq/test_config_flow.py index 9fb3b34ca63..7620a9ad176 100644 --- a/tests/components/myq/test_config_flow.py +++ b/tests/components/myq/test_config_flow.py @@ -111,10 +111,18 @@ async def test_form_homekit(hass): await setup.async_setup_component(hass, "persistent_notification", {}) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "form" assert result["errors"] == {} + flow = next( + flow + for flow in hass.config_entries.flow.async_progress() + if flow["flow_id"] == result["flow_id"] + ) + assert flow["context"]["unique_id"] == "AA:BB:CC:DD:EE:FF" entry = MockConfigEntry( domain=DOMAIN, data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"} @@ -122,6 +130,8 @@ async def test_form_homekit(hass): entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "homekit"} + DOMAIN, + context={"source": "homekit"}, + data={"properties": {"id": "AA:BB:CC:DD:EE:FF"}}, ) assert result["type"] == "abort" From b44464e0fa46fcff4a503f216efcbf7ded10ce3d Mon Sep 17 00:00:00 2001 From: Quentame Date: Sun, 26 Apr 2020 00:37:41 +0200 Subject: [PATCH 064/511] Fix Synology DSM translation (#34696) --- homeassistant/components/synology_dsm/strings.json | 2 +- homeassistant/components/synology_dsm/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index d00525f995d..c58b0d819ea 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -30,7 +30,7 @@ } }, "error": { - "connection": "Connection error: please check your host, password & ssl", + "connection": "Connection error: please check your host, port & ssl", "login": "Login error: please check your username & password", "missing_data": "Missing data: please retry later or an other configuration", "otp_failed": "Two-step authentication failed, retry with a new pass code", diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index dd85dd12950..60a17d703de 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Host already configured" }, "error": { - "connection": "Connection error: please check your host, password & ssl", + "connection": "Connection error: please check your host, port & ssl", "login": "Login error: please check your username & password", "missing_data": "Missing data: please retry later or an other configuration", "otp_failed": "Two-step authentication failed, retry with a new pass code", From 147dfa06afe3330243b1702f13a05ca5c8ebf115 Mon Sep 17 00:00:00 2001 From: escoand Date: Sun, 26 Apr 2020 00:40:26 +0200 Subject: [PATCH 065/511] Fix fritzbox integration errors (#34639) --- homeassistant/components/fritzbox/config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 1b086f58159..ffcdb499c30 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -83,7 +83,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data[CONF_HOST] == user_input[CONF_HOST]: + if entry.data.get(CONF_HOST) == user_input[CONF_HOST]: if entry.data != user_input: self.hass.config_entries.async_update_entry( entry, data=user_input @@ -117,7 +117,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_in_progress") for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data[CONF_HOST] == host: + if entry.data.get(CONF_HOST) == host: if entry.data != user_input: self.hass.config_entries.async_update_entry(entry, data=user_input) return self.async_abort(reason="already_configured") From 5760fb94fe732454df1753fb24e133ca5757745d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 26 Apr 2020 00:40:44 +0200 Subject: [PATCH 066/511] Add retry at startup (#34656) --- homeassistant/components/webostv/__init__.py | 32 ++++++++++++++----- tests/components/webostv/test_media_player.py | 1 + 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 0790ece9333..f0a059fc5b8 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -28,6 +28,7 @@ from homeassistant.const import ( ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_call_later from .const import ATTR_SOUND_OUTPUT @@ -138,15 +139,30 @@ async def async_setup_tv_finalize(hass, config, conf, client): client.clear_state_update_callbacks() await client.disconnect() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_on_stop) + async def async_load_platforms(_): + """Load platforms and event listener.""" + await async_connect(client) - await async_connect(client) - hass.async_create_task( - hass.helpers.discovery.async_load_platform("media_player", DOMAIN, conf, config) - ) - hass.async_create_task( - hass.helpers.discovery.async_load_platform("notify", DOMAIN, conf, config) - ) + if client.connection is None: + async_call_later(hass, 60, async_load_platforms) + _LOGGER.warning( + "No connection could be made with host %s, retrying in 60 seconds", + conf.get(CONF_HOST), + ) + return + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_on_stop) + + hass.async_create_task( + hass.helpers.discovery.async_load_platform( + "media_player", DOMAIN, conf, config + ) + ) + hass.async_create_task( + hass.helpers.discovery.async_load_platform("notify", DOMAIN, conf, config) + ) + + await async_load_platforms(None) async def async_request_configuration(hass, config, conf, client): diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 2685064a946..9ba35a510dd 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -41,6 +41,7 @@ def client_fixture(): "homeassistant.components.webostv.WebOsClient", autospec=True ) as mock_client_class: client = mock_client_class.return_value + client.connection = True client.software_info = {"device_id": "a1:b1:c1:d1:e1:f1"} yield client From e7f8d6bbf7d17460a3be46f7f1858a5d490b67ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Apr 2020 15:52:50 -0700 Subject: [PATCH 067/511] Fix more tests on Python 3.8 (#34703) --- tests/auth/providers/test_homeassistant.py | 16 +++--------- tests/helpers/test_config_entry_flow.py | 16 +++++------- tests/helpers/test_service.py | 30 ++++++++-------------- tests/helpers/test_storage.py | 6 ++--- tests/test_config_entries.py | 6 ++--- tests/util/test_location.py | 13 +++++----- tests/util/test_package.py | 2 +- 7 files changed, 34 insertions(+), 55 deletions(-) diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py index 9a275c78ba6..9cfdfc30aa5 100644 --- a/tests/auth/providers/test_homeassistant.py +++ b/tests/auth/providers/test_homeassistant.py @@ -1,7 +1,7 @@ """Test the Home Assistant local auth provider.""" import asyncio -from unittest.mock import Mock, patch +from asynctest import Mock, patch import pytest import voluptuous as vol @@ -12,8 +12,6 @@ from homeassistant.auth.providers import ( homeassistant as hass_auth, ) -from tests.common import mock_coro - @pytest.fixture def data(hass): @@ -156,9 +154,7 @@ async def test_get_or_create_credentials(hass, data): provider = manager.auth_providers[0] provider.data = data credentials1 = await provider.async_get_or_create_credentials({"username": "hello"}) - with patch.object( - provider, "async_credentials", return_value=mock_coro([credentials1]) - ): + with patch.object(provider, "async_credentials", return_value=[credentials1]): credentials2 = await provider.async_get_or_create_credentials( {"username": "hello "} ) @@ -264,17 +260,13 @@ async def test_legacy_get_or_create_credentials(hass, legacy_data): provider.data = legacy_data credentials1 = await provider.async_get_or_create_credentials({"username": "hello"}) - with patch.object( - provider, "async_credentials", return_value=mock_coro([credentials1]) - ): + with patch.object(provider, "async_credentials", return_value=[credentials1]): credentials2 = await provider.async_get_or_create_credentials( {"username": "hello"} ) assert credentials1 is credentials2 - with patch.object( - provider, "async_credentials", return_value=mock_coro([credentials1]) - ): + with patch.object(provider, "async_credentials", return_value=[credentials1]): credentials3 = await provider.async_get_or_create_credentials( {"username": "hello "} ) diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 54003b9dd18..3126c2c0c8d 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -1,6 +1,5 @@ """Tests for the Config Entry Flow helper.""" -from unittest.mock import Mock, patch - +from asynctest import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow, setup @@ -9,7 +8,6 @@ from homeassistant.helpers import config_entry_flow from tests.common import ( MockConfigEntry, MockModule, - mock_coro, mock_entity_platform, mock_integration, ) @@ -209,8 +207,8 @@ async def test_webhook_create_cloudhook(hass, webhook_flow_conf): """Test only a single entry is allowed.""" assert await setup.async_setup_component(hass, "cloud", {}) - async_setup_entry = Mock(return_value=mock_coro(True)) - async_unload_entry = Mock(return_value=mock_coro(True)) + async_setup_entry = Mock(return_value=True) + async_unload_entry = Mock(return_value=True) mock_integration( hass, @@ -228,10 +226,9 @@ async def test_webhook_create_cloudhook(hass, webhook_flow_conf): ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - coro = mock_coro({"cloudhook_url": "https://example.com"}) - with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", return_value=coro + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", + return_value={"cloudhook_url": "https://example.com"}, ) as mock_create, patch( "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( @@ -246,7 +243,8 @@ async def test_webhook_create_cloudhook(hass, webhook_flow_conf): assert len(async_setup_entry.mock_calls) == 1 with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_delete", return_value=coro + "hass_nabucasa.cloudhooks.Cloudhooks.async_delete", + return_value={"cloudhook_url": "https://example.com"}, ) as mock_delete: result = await hass.config_entries.async_remove(result["result"].entry_id) diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 04d0ec64b83..8819684e8d6 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -2,8 +2,8 @@ from collections import OrderedDict from copy import deepcopy import unittest -from unittest.mock import Mock, patch +from asynctest import CoroutineMock, Mock, patch import pytest import voluptuous as vol @@ -30,7 +30,6 @@ from homeassistant.setup import async_setup_component from tests.common import ( MockEntity, get_test_home_assistant, - mock_coro, mock_device_registry, mock_registry, mock_service, @@ -40,10 +39,7 @@ from tests.common import ( @pytest.fixture def mock_handle_entity_call(): """Mock service platform call.""" - with patch( - "homeassistant.helpers.service._handle_entity_call", - side_effect=lambda *args: mock_coro(), - ) as mock_call: + with patch("homeassistant.helpers.service._handle_entity_call") as mock_call: yield mock_call @@ -310,7 +306,7 @@ async def test_async_get_all_descriptions(hass): async def test_call_with_required_features(hass, mock_entities): """Test service calls invoked only if entity has required feautres.""" - test_service_mock = Mock(return_value=mock_coro()) + test_service_mock = CoroutineMock(return_value=None) await service.entity_service_call( hass, [Mock(entities=mock_entities)], @@ -374,11 +370,9 @@ async def test_call_context_target_all(hass, mock_handle_entity_call, mock_entit """Check we only target allowed entities if targeting all.""" with patch( "homeassistant.auth.AuthManager.async_get_user", - return_value=mock_coro( - Mock( - permissions=PolicyPermissions( - {"entities": {"entity_ids": {"light.kitchen": True}}}, None - ) + return_value=Mock( + permissions=PolicyPermissions( + {"entities": {"entity_ids": {"light.kitchen": True}}}, None ) ), ): @@ -404,11 +398,9 @@ async def test_call_context_target_specific( """Check targeting specific entities.""" with patch( "homeassistant.auth.AuthManager.async_get_user", - return_value=mock_coro( - Mock( - permissions=PolicyPermissions( - {"entities": {"entity_ids": {"light.kitchen": True}}}, None - ) + return_value=Mock( + permissions=PolicyPermissions( + {"entities": {"entity_ids": {"light.kitchen": True}}}, None ) ), ): @@ -435,7 +427,7 @@ async def test_call_context_target_specific_no_auth( with pytest.raises(exceptions.Unauthorized) as err: with patch( "homeassistant.auth.AuthManager.async_get_user", - return_value=mock_coro(Mock(permissions=PolicyPermissions({}, None))), + return_value=Mock(permissions=PolicyPermissions({}, None)), ): await service.entity_service_call( hass, @@ -606,7 +598,7 @@ async def test_domain_control_unknown(hass, mock_entities): with patch( "homeassistant.helpers.entity_registry.async_get_registry", - return_value=mock_coro(Mock(entities=mock_entities)), + return_value=Mock(entities=mock_entities), ): protected_mock_service = hass.helpers.service.verify_domain_control( "test_domain" diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 61648c85ada..1966430113c 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -2,8 +2,8 @@ import asyncio from datetime import timedelta import json -from unittest.mock import Mock, patch +from asynctest import Mock, patch import pytest from homeassistant.const import ( @@ -14,7 +14,7 @@ from homeassistant.core import CoreState from homeassistant.helpers import storage from homeassistant.util import dt -from tests.common import async_fire_time_changed, mock_coro +from tests.common import async_fire_time_changed MOCK_VERSION = 1 MOCK_KEY = "storage-test" @@ -189,7 +189,7 @@ async def test_writing_while_writing_delay(hass, store, hass_storage): async def test_migrator_no_existing_config(hass, store, hass_storage): """Test migrator with no existing config.""" with patch("os.path.isfile", return_value=False), patch.object( - store, "async_load", return_value=mock_coro({"cur": "config"}) + store, "async_load", return_value={"cur": "config"} ): data = await storage.async_migrator(hass, "old-path", store) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 28746bbfbe0..6d5c735b882 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -1,9 +1,8 @@ """Test the config manager.""" import asyncio from datetime import timedelta -from unittest.mock import MagicMock, patch -from asynctest import CoroutineMock +from asynctest import CoroutineMock, MagicMock, patch import pytest from homeassistant import config_entries, data_entry_flow, loader @@ -935,8 +934,7 @@ async def test_init_custom_integration(hass): ) with pytest.raises(data_entry_flow.UnknownHandler): with patch( - "homeassistant.loader.async_get_integration", - return_value=mock_coro(integration), + "homeassistant.loader.async_get_integration", return_value=integration, ): await hass.config_entries.flow.async_init("bla") diff --git a/tests/util/test_location.py b/tests/util/test_location.py index 3f03619a052..968c6257f37 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -1,12 +1,11 @@ """Test Home Assistant location util methods.""" -from unittest.mock import Mock, patch - import aiohttp +from asynctest import Mock, patch import pytest import homeassistant.util.location as location_util -from tests.common import load_fixture, mock_coro +from tests.common import load_fixture # Paris COORDINATES_PARIS = (48.864716, 2.349014) @@ -109,7 +108,7 @@ async def test_detect_location_info_ip_api(aioclient_mock, session): """Test detect location info using ip-api.com.""" aioclient_mock.get(location_util.IP_API, text=load_fixture("ip-api.com.json")) - with patch("homeassistant.util.location._get_ipapi", return_value=mock_coro(None)): + with patch("homeassistant.util.location._get_ipapi", return_value=None): info = await location_util.async_detect_location_info(session, _test_real=True) assert info is not None @@ -128,9 +127,9 @@ async def test_detect_location_info_ip_api(aioclient_mock, session): async def test_detect_location_info_both_queries_fail(session): """Ensure we return None if both queries fail.""" - with patch( - "homeassistant.util.location._get_ipapi", return_value=mock_coro(None) - ), patch("homeassistant.util.location._get_ip_api", return_value=mock_coro(None)): + with patch("homeassistant.util.location._get_ipapi", return_value=None), patch( + "homeassistant.util.location._get_ip_api", return_value=None + ): info = await location_util.async_detect_location_info(session, _test_real=True) assert info is None diff --git a/tests/util/test_package.py b/tests/util/test_package.py index ca4ed83734a..f7cce0e298f 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -4,8 +4,8 @@ import logging import os from subprocess import PIPE import sys -from unittest.mock import MagicMock, call, patch +from asynctest import MagicMock, call, patch import pkg_resources import pytest From 9ca2ad53f97c75972d6c830dea7a06ae39a1f473 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 26 Apr 2020 01:54:41 +0200 Subject: [PATCH 068/511] Python 3.8 on CI (#34654) --- Dockerfile.dev | 9 +- azure-pipelines-ci.yml | 379 +++++++++++++++++++++-------------------- 2 files changed, 197 insertions(+), 191 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index fa90a84fc1e..40f281b95eb 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,10 @@ -FROM python:3.7 +FROM python:3.8 -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ +RUN \ + && apt-get update && apt-get install -y --no-install-recommends \ + software-properties-common \ + && add-apt-repository ppa:jonathonf/ffmpeg-4 \ + && apt-get update && apt-get install -y --no-install-recommends \ libudev-dev \ libavformat-dev \ libavcodec-dev \ diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index af323ecde1a..d620cd14594 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -4,9 +4,9 @@ trigger: batch: true branches: include: - - rc - - dev - - master + - rc + - dev + - master pr: - rc - dev @@ -14,205 +14,208 @@ pr: resources: containers: - - container: 37 - image: homeassistant/ci-azure:3.7 + - container: 37 + image: homeassistant/ci-azure:3.7 + - container: 38 + image: homeassistant/ci-azure:3.8 repositories: - repository: azure type: github - name: 'home-assistant/ci-azure' - endpoint: 'home-assistant' + name: "home-assistant/ci-azure" + endpoint: "home-assistant" variables: - name: PythonMain - value: '37' + value: "37" stages: + - stage: "Overview" + jobs: + - job: "Lint" + pool: + vmImage: "ubuntu-latest" + container: $[ variables['PythonMain'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "requirements_test.txt | homeassistant/package_constraints.txt" + build: | + python -m venv venv -- stage: 'Overview' - jobs: - - job: 'Lint' - pool: - vmImage: 'ubuntu-latest' - container: $[ variables['PythonMain'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'requirements_test.txt | homeassistant/package_constraints.txt' - build: | - python -m venv venv + . venv/bin/activate + pip install -r requirements_test.txt -c homeassistant/package_constraints.txt + pre-commit install-hooks + - script: | + . venv/bin/activate + pre-commit run --hook-stage manual check-executables-have-shebangs --all-files + displayName: "Run executables check" + - script: | + . venv/bin/activate + pre-commit run codespell --all-files + displayName: "Run codespell" + - script: | + . venv/bin/activate + pre-commit run flake8 --all-files + displayName: "Run flake8" + - script: | + . venv/bin/activate + pre-commit run bandit --all-files + displayName: "Run bandit" + - script: | + . venv/bin/activate + pre-commit run isort --all-files --show-diff-on-failure + displayName: "Run isort" + - script: | + . venv/bin/activate + pre-commit run check-json --all-files + displayName: "Run check-json" + - script: | + . venv/bin/activate + pre-commit run yamllint --all-files + displayName: "Run yamllint" + - script: | + . venv/bin/activate + pre-commit run pyupgrade --all-files --show-diff-on-failure + displayName: "Run pyupgrade" + # Prettier seems to hang on Azure, unknown why yet. + # Temporarily disable the check to no block PRs + # - script: | + # . venv/bin/activate + # pre-commit run prettier --all-files --show-diff-on-failure + # displayName: 'Run prettier' + - job: "Validate" + pool: + vmImage: "ubuntu-latest" + container: $[ variables['PythonMain'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "homeassistant/package_constraints.txt" + build: | + python -m venv venv - . venv/bin/activate - pip install -r requirements_test.txt -c homeassistant/package_constraints.txt - pre-commit install-hooks - - script: | - . venv/bin/activate - pre-commit run --hook-stage manual check-executables-have-shebangs --all-files - displayName: 'Run executables check' - - script: | - . venv/bin/activate - pre-commit run codespell --all-files - displayName: 'Run codespell' - - script: | - . venv/bin/activate - pre-commit run flake8 --all-files - displayName: 'Run flake8' - - script: | - . venv/bin/activate - pre-commit run bandit --all-files - displayName: 'Run bandit' - - script: | - . venv/bin/activate - pre-commit run isort --all-files --show-diff-on-failure - displayName: 'Run isort' - - script: | - . venv/bin/activate - pre-commit run check-json --all-files - displayName: 'Run check-json' - - script: | - . venv/bin/activate - pre-commit run yamllint --all-files - displayName: 'Run yamllint' - - script: | - . venv/bin/activate - pre-commit run pyupgrade --all-files --show-diff-on-failure - displayName: 'Run pyupgrade' - # Prettier seems to hang on Azure, unknown why yet. - # Temporarily disable the check to no block PRs - # - script: | - # . venv/bin/activate - # pre-commit run prettier --all-files --show-diff-on-failure - # displayName: 'Run prettier' - - job: 'Validate' - pool: - vmImage: 'ubuntu-latest' - container: $[ variables['PythonMain'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'homeassistant/package_constraints.txt' - build: | - python -m venv venv + . venv/bin/activate + pip install -e . + - script: | + . venv/bin/activate + python -m script.hassfest --action validate + displayName: "Validate manifests" + - script: | + . venv/bin/activate + ./script/gen_requirements_all.py validate + displayName: "requirements_all validate" + - job: "CheckFormat" + pool: + vmImage: "ubuntu-latest" + container: $[ variables['PythonMain'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "requirements_test.txt | homeassistant/package_constraints.txt" + build: | + python -m venv venv - . venv/bin/activate - pip install -e . - - script: | - . venv/bin/activate - python -m script.hassfest --action validate - displayName: 'Validate manifests' - - script: | - . venv/bin/activate - ./script/gen_requirements_all.py validate - displayName: 'requirements_all validate' - - job: 'CheckFormat' - pool: - vmImage: 'ubuntu-latest' - container: $[ variables['PythonMain'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'requirements_test.txt | homeassistant/package_constraints.txt' - build: | - python -m venv venv + . venv/bin/activate + pip install -r requirements_test.txt -c homeassistant/package_constraints.txt + pre-commit install-hooks + - script: | + . venv/bin/activate + pre-commit run black --all-files --show-diff-on-failure + displayName: "Check Black formatting" - . venv/bin/activate - pip install -r requirements_test.txt -c homeassistant/package_constraints.txt - pre-commit install-hooks - - script: | - . venv/bin/activate - pre-commit run black --all-files --show-diff-on-failure - displayName: 'Check Black formatting' + - stage: "Tests" + dependsOn: + - "Overview" + jobs: + - job: "PyTest" + pool: + vmImage: "ubuntu-latest" + strategy: + maxParallel: 3 + matrix: + Python37: + python.container: "37" + Python38: + python.container: "38" + container: $[ variables['python.container'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "requirements_test_all.txt | homeassistant/package_constraints.txt" + build: | + set -e + python -m venv venv -- stage: 'Tests' - dependsOn: - - 'Overview' - jobs: - - job: 'PyTest' - pool: - vmImage: 'ubuntu-latest' - strategy: - maxParallel: 3 - matrix: - Python37: - python.container: '37' - container: $[ variables['python.container'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'requirements_test_all.txt | homeassistant/package_constraints.txt' - build: | - set -e - python -m venv venv + . venv/bin/activate + pip install -U pip setuptools pytest-azurepipelines pytest-xdist -c homeassistant/package_constraints.txt + pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt + # This is a TEMP. Eventually we should make sure our 4 dependencies drop typing. + # Find offending deps with `pipdeptree -r -p typing` + pip uninstall -y typing + - script: | + . venv/bin/activate + pip install -e . + displayName: "Install Home Assistant" + - script: | + set -e - . venv/bin/activate - pip install -U pip setuptools pytest-azurepipelines pytest-xdist -c homeassistant/package_constraints.txt - pip install -r requirements_test_all.txt -c homeassistant/package_constraints.txt - # This is a TEMP. Eventually we should make sure our 4 dependencies drop typing. - # Find offending deps with `pipdeptree -r -p typing` - pip uninstall -y typing - - script: | - . venv/bin/activate - pip install -e . - displayName: 'Install Home Assistant' - - script: | - set -e + . venv/bin/activate + pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar tests + script/check_dirty + displayName: "Run pytest for python $(python.container)" + condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain'])) + - script: | + set -e - . venv/bin/activate - pytest --timeout=9 --durations=10 -n auto --dist=loadfile -qq -o console_output_style=count -p no:sugar tests - script/check_dirty - displayName: 'Run pytest for python $(python.container)' - condition: and(succeeded(), ne(variables['python.container'], variables['PythonMain'])) - - script: | - set -e + . venv/bin/activate + pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests + codecov --token $(codecovToken) + script/check_dirty + displayName: "Run pytest for python $(python.container) / coverage" + condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain'])) - . venv/bin/activate - pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests - codecov --token $(codecovToken) - script/check_dirty - displayName: 'Run pytest for python $(python.container) / coverage' - condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain'])) + - stage: "FullCheck" + dependsOn: + - "Overview" + jobs: + - job: "Pylint" + pool: + vmImage: "ubuntu-latest" + container: $[ variables['PythonMain'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "requirements_all.txt | requirements_test.txt | homeassistant/package_constraints.txt" + build: | + set -e + python -m venv venv -- stage: 'FullCheck' - dependsOn: - - 'Overview' - jobs: - - job: 'Pylint' - pool: - vmImage: 'ubuntu-latest' - container: $[ variables['PythonMain'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'requirements_all.txt | requirements_test.txt | homeassistant/package_constraints.txt' - build: | - set -e - python -m venv venv + . venv/bin/activate + pip install -U pip setuptools wheel + pip install -r requirements_all.txt -c homeassistant/package_constraints.txt + pip install -r requirements_test.txt -c homeassistant/package_constraints.txt + - script: | + . venv/bin/activate + pip install -e . + displayName: "Install Home Assistant" + - script: | + . venv/bin/activate + pylint homeassistant + displayName: "Run pylint" + - job: "Mypy" + pool: + vmImage: "ubuntu-latest" + container: $[ variables['PythonMain'] ] + steps: + - template: templates/azp-step-cache.yaml@azure + parameters: + keyfile: "requirements_test.txt | setup.py | homeassistant/package_constraints.txt" + build: | + python -m venv venv - . venv/bin/activate - pip install -U pip setuptools wheel - pip install -r requirements_all.txt -c homeassistant/package_constraints.txt - pip install -r requirements_test.txt -c homeassistant/package_constraints.txt - - script: | - . venv/bin/activate - pip install -e . - displayName: 'Install Home Assistant' - - script: | - . venv/bin/activate - pylint homeassistant - displayName: 'Run pylint' - - job: 'Mypy' - pool: - vmImage: 'ubuntu-latest' - container: $[ variables['PythonMain'] ] - steps: - - template: templates/azp-step-cache.yaml@azure - parameters: - keyfile: 'requirements_test.txt | setup.py | homeassistant/package_constraints.txt' - build: | - python -m venv venv - - . venv/bin/activate - pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt - pre-commit install-hooks - - script: | - . venv/bin/activate - pre-commit run mypy --all-files - displayName: 'Run mypy' + . venv/bin/activate + pip install -e . -r requirements_test.txt -c homeassistant/package_constraints.txt + pre-commit install-hooks + - script: | + . venv/bin/activate + pre-commit run mypy --all-files + displayName: "Run mypy" From 90e0a1af8a5923a3a863fe9fee27daba25f5df07 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 26 Apr 2020 00:02:45 +0000 Subject: [PATCH 069/511] [ci skip] Translation update --- .../components/abode/translations/ko.json | 2 +- .../components/adguard/translations/ko.json | 2 +- .../components/airvisual/translations/de.json | 28 ++++++++++++-- .../components/airvisual/translations/ru.json | 8 ++-- .../components/almond/translations/ko.json | 2 +- .../components/atag/translations/de.json | 20 ++++++++++ .../components/braviatv/translations/ko.json | 38 +++++++++++++++++++ .../components/cast/translations/ko.json | 2 +- .../components/deconz/translations/ko.json | 8 ++++ .../components/flume/translations/ko.json | 24 ++++++++++++ .../components/fritzbox/translations/de.json | 32 ++++++++++++++++ .../hisense_aehw4a1/translations/ko.json | 2 +- .../components/hue/translations/ko.json | 6 ++- .../components/iaqualink/translations/ko.json | 2 +- .../components/ios/translations/ko.json | 2 +- .../components/ipp/translations/ko.json | 5 ++- .../islamic_prayer_times/translations/de.json | 7 ++++ .../components/izone/translations/ko.json | 2 +- .../components/lifx/translations/ko.json | 2 +- .../components/light/translations/ko.json | 1 + .../components/local_ip/translations/ko.json | 3 ++ .../logi_circle/translations/ko.json | 2 +- .../components/mqtt/translations/ko.json | 2 +- .../components/nest/translations/ko.json | 6 +-- .../components/netatmo/translations/ko.json | 2 +- .../components/nut/translations/ko.json | 16 +++++++- .../components/nws/translations/ko.json | 23 +++++++++++ .../components/point/translations/ko.json | 2 +- .../components/roomba/translations/ko.json | 32 ++++++++++++++++ .../season/translations/sensor.de.json | 6 +++ .../season/translations/sensor.ko.json | 6 +++ .../smartthings/translations/ko.json | 21 ++++++++++ .../components/soma/translations/ko.json | 2 +- .../components/somfy/translations/ko.json | 2 +- .../components/sonos/translations/ko.json | 2 +- .../components/spotify/translations/ko.json | 2 +- .../synology_dsm/translations/ca.json | 1 - .../synology_dsm/translations/de.json | 3 +- .../synology_dsm/translations/es.json | 1 - .../synology_dsm/translations/fr.json | 11 +++++- .../synology_dsm/translations/ko.json | 12 ++++++ .../synology_dsm/translations/lb.json | 1 - .../synology_dsm/translations/no.json | 1 - .../synology_dsm/translations/ru.json | 3 +- .../synology_dsm/translations/zh-Hant.json | 1 - .../components/tado/translations/ko.json | 33 ++++++++++++++++ .../totalconnect/translations/ko.json | 19 ++++++++++ .../components/tplink/translations/ko.json | 2 +- .../components/upnp/translations/de.json | 2 +- .../components/upnp/translations/ko.json | 2 +- .../components/wemo/translations/ko.json | 2 +- .../components/zha/translations/ko.json | 2 +- 52 files changed, 375 insertions(+), 45 deletions(-) create mode 100644 homeassistant/components/atag/translations/de.json create mode 100644 homeassistant/components/braviatv/translations/ko.json create mode 100644 homeassistant/components/flume/translations/ko.json create mode 100644 homeassistant/components/fritzbox/translations/de.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/de.json create mode 100644 homeassistant/components/nws/translations/ko.json create mode 100644 homeassistant/components/roomba/translations/ko.json create mode 100644 homeassistant/components/tado/translations/ko.json create mode 100644 homeassistant/components/totalconnect/translations/ko.json diff --git a/homeassistant/components/abode/translations/ko.json b/homeassistant/components/abode/translations/ko.json index 857a460fa0f..46363382407 100644 --- a/homeassistant/components/abode/translations/ko.json +++ b/homeassistant/components/abode/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 Abode \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Abode \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "connection_error": "Abode \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index 95ce591747e..1bcc60c80f0 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -4,7 +4,7 @@ "adguard_home_addon_outdated": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 AdGuard Home {minimal_version} \uc774\uc0c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud604\uc7ac \ubc84\uc804\uc740 {current_version} \uc785\ub2c8\ub2e4. Hass.io AdGuard Home \uc560\ub4dc\uc628\uc744 \uc5c5\ub370\uc774\ud2b8 \ud574\uc8fc\uc138\uc694.", "adguard_home_outdated": "\uc774 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 AdGuard Home {minimal_version} \uc774\uc0c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \ud604\uc7ac \ubc84\uc804\uc740 {current_version} \uc785\ub2c8\ub2e4.", "existing_instance_updated": "\uae30\uc874 \uad6c\uc131\uc744 \uc5c5\ub370\uc774\ud2b8\ud588\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 AdGuard Home \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 AdGuard Home \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index 82e4c122c33..a8b2d296560 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -1,19 +1,39 @@ { "config": { "abort": { - "already_configured": "Diese Koordinaten wurden bereits registriert." + "already_configured": "Diese Koordinaten oder Node/Pro ID sind bereits registriert." }, "error": { - "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel" + "general_error": "Es gab einen unbekannten Fehler.", + "invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel bereitgestellt.", + "unable_to_connect": "Verbindung zum Node/Pro-Ger\u00e4t nicht m\u00f6glich." }, "step": { - "user": { + "geography": { "data": { "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad" + } + }, + "node_pro": { + "data": { + "ip_address": "IP-Adresse/Hostname des Ger\u00e4ts", + "password": "Ger\u00e4tekennwort" }, - "description": "\u00dcberwachen Sie die Luftqualit\u00e4t an einem geografischen Ort.", + "description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.", + "title": "Konfigurieren Sie einen AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "API-Schl\u00fcssel", + "cloud_api": "Geografische Position", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "node_pro": "AirVisual Node Pro", + "type": "Integrationstyp" + }, + "description": "W\u00e4hlen Sie aus, welche Art von AirVisual-Daten Sie \u00fcberwachen m\u00f6chten.", "title": "Konfigurieren Sie AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/ru.json b/homeassistant/components/airvisual/translations/ru.json index 3479d59ee90..ecc8999fd18 100644 --- a/homeassistant/components/airvisual/translations/ru.json +++ b/homeassistant/components/airvisual/translations/ru.json @@ -15,15 +15,15 @@ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430" }, - "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e API AirVisual.", + "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u0433\u043e API AirVisual.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f" }, "node_pro": { "data": { - "ip_address": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441", + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441/\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0412\u0430\u0448\u0435\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AirVisual Node / Pro" }, "user": { @@ -33,7 +33,7 @@ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", "node_pro": "AirVisual Node Pro", - "type": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438" + "type": "\u0422\u0438\u043f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u0434\u0430\u043d\u043d\u044b\u0445 AirVisual, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c.", "title": "AirVisual" diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index f8791895d50..695ca3a752c 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/atag/translations/de.json b/homeassistant/components/atag/translations/de.json new file mode 100644 index 00000000000..f9d40a035a3 --- /dev/null +++ b/homeassistant/components/atag/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Nur ein Atag-Ger\u00e4t kann mit Home Assistant verbunden werden." + }, + "error": { + "connection_error": "Verbindung fehlgeschlagen, versuchen Sie es erneut" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Port (10000)" + }, + "title": "Stellen Sie eine Verbindung zum Ger\u00e4t her" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json new file mode 100644 index 00000000000..3652210f7b7 --- /dev/null +++ b/homeassistant/components/braviatv/translations/ko.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8 \ub610\ub294 PIN \ucf54\ub4dc\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": { + "authorize": { + "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.", + "title": "Sony Bravia TV \uc2b9\uc778\ud558\uae30" + }, + "user": { + "data": { + "host": "TV \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c" + }, + "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" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "\ubb34\uc2dc\ub41c \uc785\ub825 \uc18c\uc2a4 \ubaa9\ub85d" + }, + "title": "Sony Bravia TV \uc635\uc158" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index c41b2407792..1de8e74ec84 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Google \uce90\uc2a4\ud2b8 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Google \uce90\uc2a4\ud2b8\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Google \uce90\uc2a4\ud2b8\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index c3704326d33..e6e22abc332 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -29,12 +29,19 @@ "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" } + }, + "manual_input": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + } } } }, "device_automation": { "trigger_subtype": { "both_buttons": "\ub450 \uac1c", + "bottom_buttons": "\ud558\ub2e8 \ubc84\ud2bc", "button_1": "\uccab \ubc88\uc9f8", "button_2": "\ub450 \ubc88\uc9f8", "button_3": "\uc138 \ubc88\uc9f8", @@ -51,6 +58,7 @@ "side_4": "\uba74 4", "side_5": "\uba74 5", "side_6": "\uba74 6", + "top_buttons": "\uc0c1\ub2e8 \ubc84\ud2bc", "turn_off": "\ub044\uae30", "turn_on": "\ucf1c\uae30" }, diff --git a/homeassistant/components/flume/translations/ko.json b/homeassistant/components/flume/translations/ko.json new file mode 100644 index 00000000000..faac5e9c579 --- /dev/null +++ b/homeassistant/components/flume/translations/ko.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 \uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "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": { + "client_id": "\ud074\ub77c\uc774\uc5b8\ud2b8 ID", + "client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf", + "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.", + "title": "Flume \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json new file mode 100644 index 00000000000..5f16553c64c --- /dev/null +++ b/homeassistant/components/fritzbox/translations/de.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Diese AVM FRITZ! Box ist bereits konfiguriert.", + "already_in_progress": "Die Konfiguration der AVM FRITZ! Box ist bereits in Bearbeitung.", + "not_found": "Keine unterst\u00fctzte AVM FRITZ! Box im Netzwerk gefunden." + }, + "error": { + "auth_failed": "Benutzername und/oder Passwort sind falsch." + }, + "flow_title": "AVM FRITZ! Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "description": "M\u00f6chten Sie {name} einrichten?", + "title": "AVM FRITZ! Box" + }, + "user": { + "data": { + "host": "Host oder IP-Adresse", + "password": "Passwort", + "username": "Benutzername" + }, + "description": "Geben Sie Ihre AVM FRITZ! Box-Informationen ein.", + "title": "AVM FRITZ! Box" + } + } + } +} \ 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 18199edf856..2c472277b00 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": "Hisense AEH-W4A1 \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Hisense AEH-W4A1 \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Hisense AEH-W4A1 \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 31bf427196c..561b3442d2e 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -35,13 +35,17 @@ "button_4": "\ub124 \ubc88\uc9f8 \ubc84\ud2bc", "dim_down": "\uc5b4\ub461\uac8c \ud558\uae30", "dim_up": "\ubc1d\uac8c \ud558\uae30", + "double_buttons_1_3": "\uccab \ubc88\uc9f8 \ubc0f \uc138 \ubc88\uc9f8 \ubc84\ud2bc", + "double_buttons_2_4": "\ub450 \ubc88\uc9f8 \ubc0f \ub124 \ubc88\uc9f8 \ubc84\ud2bc", "turn_off": "\ub044\uae30", "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_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", + "remote_double_button_long_press": "\"{subtype}\" \ubaa8\ub450 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", + "remote_double_button_short_press": "\"{subtype}\" \ubaa8\ub450 \uc190\uc744 \ub5c4 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/ko.json b/homeassistant/components/iaqualink/translations/ko.json index 23b3602744c..3399960dd33 100644 --- a/homeassistant/components/iaqualink/translations/ko.json +++ b/homeassistant/components/iaqualink/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 iAqualink \uc5f0\uacb0\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "already_setup": "\ud558\ub098\uc758 iAqualink \uc5f0\uacb0\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "connection_failure": "iAqualink \uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." diff --git a/homeassistant/components/ios/translations/ko.json b/homeassistant/components/ios/translations/ko.json index 36c18ea34fe..fdd036f26a8 100644 --- a/homeassistant/components/ios/translations/ko.json +++ b/homeassistant/components/ios/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 Home Assistant iOS \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Home Assistant iOS \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index 2f4b9a1171c..a0f79d0417f 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -3,7 +3,10 @@ "abort": { "already_configured": "\uc774 \ud504\ub9b0\ud130\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "connection_error": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud558\ub824\uba74 \uc5f0\uacb0\uc744 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4." + "connection_upgrade": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud558\ub824\uba74 \uc5f0\uacb0\uc744 \uc5c5\uadf8\ub808\uc774\ub4dc\ud574\uc57c \ud569\ub2c8\ub2e4.", + "ipp_error": "IPP \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "ipp_version_error": "\ud504\ub9b0\ud130\uc5d0\uc11c IPP \ubc84\uc804\uc744 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "parse_error": "\ud504\ub9b0\ud130\uc758 \uc751\ub2f5\uc744 \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." }, "error": { "connection_error": "\ud504\ub9b0\ud130\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json new file mode 100644 index 00000000000..c9c7107d9cf --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Es ist nur eine einzige Instanz erforderlich." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/ko.json b/homeassistant/components/izone/translations/ko.json index 8d8e15d218f..4174226ab55 100644 --- a/homeassistant/components/izone/translations/ko.json +++ b/homeassistant/components/izone/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "iZone \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 iZone \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 iZone \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/ko.json b/homeassistant/components/lifx/translations/ko.json index 8fd7a0ca754..13a5ca08152 100644 --- a/homeassistant/components/lifx/translations/ko.json +++ b/homeassistant/components/lifx/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "LIFX \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 LIFX \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 LIFX \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/light/translations/ko.json b/homeassistant/components/light/translations/ko.json index cef5c22c871..969d9b2e4d4 100644 --- a/homeassistant/components/light/translations/ko.json +++ b/homeassistant/components/light/translations/ko.json @@ -3,6 +3,7 @@ "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" diff --git a/homeassistant/components/local_ip/translations/ko.json b/homeassistant/components/local_ip/translations/ko.json index 2662eab36e7..050229dbf08 100644 --- a/homeassistant/components/local_ip/translations/ko.json +++ b/homeassistant/components/local_ip/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "\ud558\ub098\uc758 \ub85c\uceec IP \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index de6ca6f483c..5ecea7db659 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Logi Circle \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Logi Circle \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "external_error": "\ub2e4\ub978 Flow \uc5d0\uc11c \uc608\uc678\uc0ac\ud56d\uc774 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", "external_setup": "Logi Circle \uc774 \ub2e4\ub978 Flow \uc5d0\uc11c \uc131\uacf5\uc801\uc73c\ub85c \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "no_flows": "Logi Circle \uc744 \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Logi Circle \uc744 \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/logi_circle/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694." diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index 32c3f892e58..659cec20394 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 MQTT \ube0c\ub85c\ucee4\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 MQTT \ube0c\ub85c\ucee4\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index 2d65458665f..08c2f54ba7a 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Nest \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Nest \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \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.", "no_flows": "Nest \ub97c \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Nest \ub97c \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/nest/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694." @@ -22,9 +22,9 @@ }, "link": { "data": { - "code": "\ud540 \ucf54\ub4dc" + "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 \ud540 \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\uc758 PIN \ucf54\ub4dc\ub97c \ubcf5\uc0ac\ud558\uc5ec \ubd99\uc5ec\ub123\uc73c\uc138\uc694.", "title": "Nest \uacc4\uc815 \uc5f0\uacb0" } } diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index f5749ebf2ca..624298c04e5 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Netatmo \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Netatmo \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.", "missing_configuration": "Netatmo \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index 74d1e6981b9..6a74c3969b6 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -8,6 +8,19 @@ "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "step": { + "resources": { + "data": { + "resources": "\ub9ac\uc18c\uc2a4" + }, + "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \ub9ac\uc18c\uc2a4 \uc120\ud0dd" + }, + "ups": { + "data": { + "alias": "\ubcc4\uba85", + "resources": "\ub9ac\uc18c\uc2a4" + }, + "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 UPS \uc120\ud0dd" + }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8", @@ -23,7 +36,8 @@ "step": { "init": { "data": { - "resources": "\ub9ac\uc18c\uc2a4" + "resources": "\ub9ac\uc18c\uc2a4", + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" }, "description": "\uc13c\uc11c \ub9ac\uc18c\uc2a4 \uc120\ud0dd" } diff --git a/homeassistant/components/nws/translations/ko.json b/homeassistant/components/nws/translations/ko.json new file mode 100644 index 00000000000..3b6eae14ba7 --- /dev/null +++ b/homeassistant/components/nws/translations/ko.json @@ -0,0 +1,23 @@ +{ + "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. \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 (\uc774\uba54\uc77c)", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "station": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc" + }, + "description": "METAR \uc2a4\ud14c\uc774\uc158 \ucf54\ub4dc\ub97c \uc9c0\uc815\ud558\uc9c0 \uc54a\uc73c\uba74 \uac00\uae4c\uc6b4 \uc2a4\ud14c\uc774\uc158\uc744 \ucc3e\ub294\ub370 \uc704\ub3c4\uc640 \uacbd\ub3c4\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.", + "title": "\ubbf8\uad6d \uae30\uc0c1\uccad\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index 1f8ca1cc107..a121d9bb460 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "Point \uacc4\uc815 \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Point \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \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 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json new file mode 100644 index 00000000000..f7278a6c43c --- /dev/null +++ b/homeassistant/components/roomba/translations/ko.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \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": { + "blid": "BLID", + "certificate": "\uc778\uc99d\uc11c", + "continuous": "\uc5f0\uc18d", + "delay": "\uc9c0\uc5f0", + "host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c", + "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", + "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "\uc5f0\uc18d", + "delay": "\uc9c0\uc5f0" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.de.json b/homeassistant/components/season/translations/sensor.de.json index b58e39a6217..5449e1b553b 100644 --- a/homeassistant/components/season/translations/sensor.de.json +++ b/homeassistant/components/season/translations/sensor.de.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "Herbst", + "spring": "Fr\u00fchling", + "summer": "Sommer", + "winter": "Winter" + }, "season__season__": { "autumn": "Herbst", "spring": "Fr\u00fchling", diff --git a/homeassistant/components/season/translations/sensor.ko.json b/homeassistant/components/season/translations/sensor.ko.json index aacabe11ed5..2af78145755 100644 --- a/homeassistant/components/season/translations/sensor.ko.json +++ b/homeassistant/components/season/translations/sensor.ko.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "\uac00\uc744", + "spring": "\ubd04", + "summer": "\uc5ec\ub984", + "winter": "\uaca8\uc6b8" + }, "season__season__": { "autumn": "\uac00\uc744", "spring": "\ubd04", diff --git a/homeassistant/components/smartthings/translations/ko.json b/homeassistant/components/smartthings/translations/ko.json index 9c573476511..383b1a9412c 100644 --- a/homeassistant/components/smartthings/translations/ko.json +++ b/homeassistant/components/smartthings/translations/ko.json @@ -1,5 +1,9 @@ { "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 \ud6c4\ud06c 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.", "token_forbidden": "\ud1a0\ud070\uc5d0 \ud544\uc694\ud55c OAuth \ubc94\uc704\ubaa9\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", @@ -8,6 +12,23 @@ "webhook_error": "SmartThings \ub294 `base_url` \uc5d0 \uc124\uc815\ub41c \uc5d4\ub4dc\ud3ec\uc778\ud2b8\uc758 \uc720\ud6a8\uc131\uc744 \uac80\uc0ac \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc758 \uc694\uad6c \uc0ac\ud56d\uc744 \uac80\ud1a0\ud574\uc8fc\uc138\uc694." }, "step": { + "authorize": { + "title": "Home Assistant \uc2b9\uc778\ud558\uae30" + }, + "pat": { + "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" + }, + "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.", + "title": "\uc704\uce58 \uc120\ud0dd\ud558\uae30" + }, "user": { "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.", "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825" diff --git a/homeassistant/components/soma/translations/ko.json b/homeassistant/components/soma/translations/ko.json index ff241531ecf..b987c7b2b73 100644 --- a/homeassistant/components/soma/translations/ko.json +++ b/homeassistant/components/soma/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Soma \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "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.", "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.", diff --git a/homeassistant/components/somfy/translations/ko.json b/homeassistant/components/somfy/translations/ko.json index a77d66c02f8..9748da483bf 100644 --- a/homeassistant/components/somfy/translations/ko.json +++ b/homeassistant/components/somfy/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Somfy \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Somfy \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.", "missing_configuration": "Somfy \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." }, diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index 8ae4c30e156..baa5200f264 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Sonos \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Sonos \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Sonos \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index ce1bba1bb5d..4892d7a45c0 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_setup": "\ud558\ub098\uc758 Spotify \uacc4\uc815\ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "already_setup": "\ud558\ub098\uc758 Spotify \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.", "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." }, diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index f593ac26182..f3ec177fb65 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -4,7 +4,6 @@ "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat" }, "error": { - "connection": "Error de connexi\u00f3: comprova l'amfitri\u00f3, la contrasenya i l'SSL", "login": "Error d\u2019inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya", "missing_data": "Falten dades: torna-ho a provar m\u00e9s tard o prova una altra configuraci\u00f3 diferent", "otp_failed": "L'autenticaci\u00f3 en dos passos ha fallat, torna-ho a provar amb un nou codi", diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index 9218af96d51..a0ca9a36273 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -6,7 +6,8 @@ "error": { "login": "Login-Fehler: Bitte \u00fcberpr\u00fcfen Sie Ihren Benutzernamen & Passwort", "missing_data": "Fehlende Daten: Bitte versuchen Sie es sp\u00e4ter noch einmal oder eine andere Konfiguration", - "otp_failed": "Die zweistufige Authentifizierung ist fehlgeschlagen. Versuchen Sie es erneut mit einem neuen Code" + "otp_failed": "Die zweistufige Authentifizierung ist fehlgeschlagen. Versuchen Sie es erneut mit einem neuen Code", + "unknown": "Unbekannter Fehler: Bitte \u00fcberpr\u00fcfen Sie die Protokolle, um weitere Details zu erhalten" }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index ad15de831c4..c2f00b0874c 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -4,7 +4,6 @@ "already_configured": "El host ya est\u00e1 configurado." }, "error": { - "connection": "Error de conexi\u00f3n: comprueba tu host, contrase\u00f1a y ssl", "login": "Error de inicio de sesi\u00f3n: comprueba tu nombre de usuario y contrase\u00f1a", "missing_data": "Faltan datos: por favor, vuelva a intentarlo m\u00e1s tarde o pruebe con otra configuraci\u00f3n", "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso", diff --git a/homeassistant/components/synology_dsm/translations/fr.json b/homeassistant/components/synology_dsm/translations/fr.json index 5577885f404..4859b10073c 100644 --- a/homeassistant/components/synology_dsm/translations/fr.json +++ b/homeassistant/components/synology_dsm/translations/fr.json @@ -4,11 +4,20 @@ "already_configured": "H\u00f4te d\u00e9j\u00e0 configur\u00e9" }, "error": { + "connection": "Erreur de connexion: veuillez v\u00e9rifier votre h\u00f4te, port et SSL", "login": "Erreur de connexion: veuillez v\u00e9rifier votre nom d'utilisateur et votre mot de passe", - "missing_data": "Donn\u00e9es manquantes: veuillez r\u00e9essayer plus tard ou utilisez une autre configuration" + "missing_data": "Donn\u00e9es manquantes: veuillez r\u00e9essayer plus tard ou utilisez une autre configuration", + "otp_failed": "\u00c9chec de l'authentification en deux \u00e9tapes, r\u00e9essayez avec un nouveau code d'acc\u00e8s", + "unknown": "Erreur inconnue: veuillez consulter les journaux pour obtenir plus de d\u00e9tails" }, "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "Code" + }, + "title": "Synology DSM: authentification en deux \u00e9tapes" + }, "link": { "data": { "api_version": "Version du DSM", diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index ff7b3b66610..d2085b7a097 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -6,7 +6,19 @@ "error": { "login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc0ac\uc6a9\uc790 \uc774\ub984 \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" }, + "flow_title": "Synology DSM {name} ({host})", "step": { + "link": { + "data": { + "api_version": "DSM \ubc84\uc804", + "password": "\ube44\ubc00\ubc88\ud638", + "port": "\ud3ec\ud2b8 (\uc120\ud0dd \uc0ac\ud56d)", + "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec NAS \uc5d0 \uc5f0\uacb0", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Synology DSM" + }, "user": { "data": { "api_version": "DSM \ubc84\uc804", diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 5453b078a3e..1db4f2a193c 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -4,7 +4,6 @@ "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { - "connection": "Feeler beim verbannen Iwwerpr\u00e9if w.e.g. den Numm, Passwuert & SSL", "login": "Feeler beim Login: iwwerpr\u00e9if de Benotzernumm & Passwuert", "missing_data": "Donn\u00e9\u00ebe feelen, prob\u00e9ier sp\u00e9ider oder mat enger aner Konfiguratioun", "otp_failed": "Feeler mam 2-Faktor-Authentifikatiouns, prob\u00e9ier mat engem neie Code", diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index 5b79e58e121..a10b28b5386 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -4,7 +4,6 @@ "already_configured": "Verten er allerede konfigurert" }, "error": { - "connection": "Tilkoblingsfeil: sjekk verten, passordet og ssl", "login": "P\u00e5loggingsfeil: Vennligst sjekk brukernavnet ditt og passordet ditt", "missing_data": "Manglende data: Pr\u00f8v p\u00e5 nytt senere eller en annen konfigurasjon", "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode", diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 837af047853..c09999b8924 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -4,11 +4,10 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { - "connection": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0445\u043e\u0441\u0442, \u043f\u0430\u0440\u043e\u043b\u044c \u0438 SSL.", "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "missing_data": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "otp_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \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, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0435\u0439" }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index 6ca0b54106d..1558008cd3b 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -4,7 +4,6 @@ "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "connection": "\u9023\u7dda\u932f\u8aa4\uff1a\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u3001\u5bc6\u78bc\u8207 SSL", "login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u5bc6\u78bc", "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", diff --git a/homeassistant/components/tado/translations/ko.json b/homeassistant/components/tado/translations/ko.json new file mode 100644 index 00000000000..08561ee43aa --- /dev/null +++ b/homeassistant/components/tado/translations/ko.json @@ -0,0 +1,33 @@ +{ + "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. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "no_homes": "\uc774 Tado \uacc4\uc815\uc5d0 \uc5f0\uacb0\ub41c \uc9d1\uc774 \uc5c6\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" + }, + "title": "Tado \uacc4\uc815\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "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.", + "title": "Tado \uc635\uc158 \uc870\uc815." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/ko.json b/homeassistant/components/totalconnect/translations/ko.json new file mode 100644 index 00000000000..fac8916a54f --- /dev/null +++ b/homeassistant/components/totalconnect/translations/ko.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc0ac\uc6a9\uc790 \uc774\ub984 \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" + }, + "step": { + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index 0d4f83366d9..45e5c525e35 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "TP-Link \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 TP-Link \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 TP-Link \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index 6dc104d71df..14f8f472221 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -26,7 +26,7 @@ "enable_sensors": "Verkehrssensoren hinzuf\u00fcgen", "igd": "UPnP/IGD" }, - "title": "Konfigurationsoptionen f\u00fcr UPnP/IGD" + "title": "Konfigurations-Optionen" } } } diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index 4cf59263fed..d1581b026cc 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -6,7 +6,7 @@ "no_devices_discovered": "\ubc1c\uacac\ub41c UPnP/IGD \uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "no_devices_found": "UPnP/IGD \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", "no_sensors_or_port_mapping": "\ucd5c\uc18c\ud55c \uc13c\uc11c \ud639\uc740 \ud3ec\ud2b8 \ub9e4\ud551\uc744 \ud65c\uc131\ud654 \ud574\uc57c \ud569\ub2c8\ub2e4", - "single_instance_allowed": "\ud558\ub098\uc758 UPnP/IGD \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 UPnP/IGD \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/ko.json b/homeassistant/components/wemo/translations/ko.json index 52f4ba34e32..41de2e3aaeb 100644 --- a/homeassistant/components/wemo/translations/ko.json +++ b/homeassistant/components/wemo/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "no_devices_found": "Wemo \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "single_instance_allowed": "\ud558\ub098\uc758 Wemo \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 Wemo \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "step": { "confirm": { diff --git a/homeassistant/components/zha/translations/ko.json b/homeassistant/components/zha/translations/ko.json index ecd936975bf..99438e10a84 100644 --- a/homeassistant/components/zha/translations/ko.json +++ b/homeassistant/components/zha/translations/ko.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\ud558\ub098\uc758 ZHA \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + "single_instance_allowed": "\ud558\ub098\uc758 ZHA \ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "ZHA \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." From d3ed80cf532b624020c0e9ab9c2622d1ecc752c4 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 26 Apr 2020 02:11:08 +0200 Subject: [PATCH 070/511] Rename VacuumDevice to VacuumEntity (#34674) --- homeassistant/components/demo/vacuum.py | 8 +++--- homeassistant/components/dyson/vacuum.py | 4 +-- homeassistant/components/ecovacs/vacuum.py | 4 +-- .../components/mqtt/vacuum/schema_legacy.py | 4 +-- .../components/mqtt/vacuum/schema_state.py | 4 +-- homeassistant/components/neato/vacuum.py | 4 +-- .../components/roomba/irobot_base.py | 4 +-- homeassistant/components/template/vacuum.py | 4 +-- homeassistant/components/vacuum/__init__.py | 28 +++++++++++++++++-- .../components/xiaomi_miio/vacuum.py | 4 +-- tests/components/demo/test_vacuum.py | 4 +-- tests/components/vacuum/test_init.py | 18 ++++++++++++ 12 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 tests/components/vacuum/test_init.py diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index 0bdf3ed48f1..a5d85aa9bd6 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -21,8 +21,8 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - StateVacuumDevice, - VacuumDevice, + StateVacuumEntity, + VacuumEntity, ) _LOGGER = logging.getLogger(__name__) @@ -95,7 +95,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class DemoVacuum(VacuumDevice): +class DemoVacuum(VacuumEntity): """Representation of a demo vacuum.""" def __init__(self, name, supported_features): @@ -254,7 +254,7 @@ class DemoVacuum(VacuumDevice): self.schedule_update_ha_state() -class StateDemoVacuum(StateVacuumDevice): +class StateDemoVacuum(StateVacuumEntity): """Representation of a demo vacuum supporting states.""" def __init__(self, name): diff --git a/homeassistant/components/dyson/vacuum.py b/homeassistant/components/dyson/vacuum.py index 6203b65c9db..2306e07072d 100644 --- a/homeassistant/components/dyson/vacuum.py +++ b/homeassistant/components/dyson/vacuum.py @@ -13,7 +13,7 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - VacuumDevice, + VacuumEntity, ) from homeassistant.helpers.icon import icon_for_battery_level @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class Dyson360EyeDevice(VacuumDevice): +class Dyson360EyeDevice(VacuumEntity): """Dyson 360 Eye robot vacuum device.""" def __init__(self, device): diff --git a/homeassistant/components/ecovacs/vacuum.py b/homeassistant/components/ecovacs/vacuum.py index 8b6115970bb..6ad51e6c474 100644 --- a/homeassistant/components/ecovacs/vacuum.py +++ b/homeassistant/components/ecovacs/vacuum.py @@ -14,7 +14,7 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - VacuumDevice, + VacuumEntity, ) from homeassistant.helpers.icon import icon_for_battery_level @@ -48,7 +48,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(vacuums, True) -class EcovacsVacuum(VacuumDevice): +class EcovacsVacuum(VacuumEntity): """Ecovacs Vacuums such as Deebot.""" def __init__(self, device): diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 85851bcf696..c4259272bb3 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -25,7 +25,7 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, - VacuumDevice, + VacuumEntity, ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_DEVICE, CONF_NAME from homeassistant.core import callback @@ -174,7 +174,7 @@ class MqttVacuum( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - VacuumDevice, + VacuumEntity, ): """Representation of a MQTT-controlled legacy vacuum.""" diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index a59beae1d34..9049df45110 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -34,7 +34,7 @@ from homeassistant.components.vacuum import ( SUPPORT_START, SUPPORT_STATUS, SUPPORT_STOP, - StateVacuumDevice, + StateVacuumEntity, ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, CONF_DEVICE, CONF_NAME from homeassistant.core import callback @@ -162,7 +162,7 @@ class MqttStateVacuum( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - StateVacuumDevice, + StateVacuumEntity, ): """Representation of a MQTT-controlled state vacuum.""" diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index 391fcecf373..a67b48169c4 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -22,7 +22,7 @@ from homeassistant.components.vacuum import ( SUPPORT_START, SUPPORT_STATE, SUPPORT_STOP, - StateVacuumDevice, + StateVacuumEntity, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE import homeassistant.helpers.config_validation as cv @@ -126,7 +126,7 @@ async def async_setup_entry(hass, entry, async_add_entities): ) -class NeatoConnectedVacuum(StateVacuumDevice): +class NeatoConnectedVacuum(StateVacuumEntity): """Representation of a Neato Connected Vacuum.""" def __init__(self, neato, robot, mapdata, persistent_maps): diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index a62ad4fdd96..cb422616e5a 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -17,7 +17,7 @@ from homeassistant.components.vacuum import ( SUPPORT_START, SUPPORT_STATE, SUPPORT_STOP, - StateVacuumDevice, + StateVacuumEntity, ) from homeassistant.helpers.entity import Entity @@ -104,7 +104,7 @@ class IRobotEntity(Entity): self.schedule_update_ha_state() -class IRobotVacuum(IRobotEntity, StateVacuumDevice): +class IRobotVacuum(IRobotEntity, StateVacuumEntity): """Base class for iRobot robots.""" def __init__(self, roomba, blid): diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index 4946d54edc3..c345663ca98 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -28,7 +28,7 @@ from homeassistant.components.vacuum import ( SUPPORT_START, SUPPORT_STATE, SUPPORT_STOP, - StateVacuumDevice, + StateVacuumEntity, ) from homeassistant.const import ( CONF_ENTITY_ID, @@ -144,7 +144,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(vacuums) -class TemplateVacuum(StateVacuumDevice): +class TemplateVacuum(StateVacuumEntity): """A template vacuum component.""" def __init__( diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index f66a1b5f226..7ef2e3889a9 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -228,7 +228,7 @@ class _BaseVacuum(Entity): ) -class VacuumDevice(_BaseVacuum, ToggleEntity): +class VacuumEntity(_BaseVacuum, ToggleEntity): """Representation of a vacuum cleaner robot.""" @property @@ -309,7 +309,19 @@ class VacuumDevice(_BaseVacuum, ToggleEntity): """Not supported.""" -class StateVacuumDevice(_BaseVacuum): +class VacuumDevice(VacuumEntity): + """Representation of a vacuum (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "VacuumDevice is deprecated, modify %s to extend VacuumEntity", + cls.__name__, + ) + + +class StateVacuumEntity(_BaseVacuum): """Representation of a vacuum cleaner robot that supports states.""" @property @@ -377,3 +389,15 @@ class StateVacuumDevice(_BaseVacuum): async def async_toggle(self, **kwargs): """Not supported.""" + + +class StateVacuumDevice(StateVacuumEntity): + """Representation of a vacuum (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "StateVacuumDevice is deprecated, modify %s to extend StateVacuumEntity", + cls.__name__, + ) diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 416918e6f43..fd144e1edc7 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -25,7 +25,7 @@ from homeassistant.components.vacuum import ( SUPPORT_START, SUPPORT_STATE, SUPPORT_STOP, - StateVacuumDevice, + StateVacuumEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -229,7 +229,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MiroboVacuum(StateVacuumDevice): +class MiroboVacuum(StateVacuumEntity): """Representation of a Xiaomi Vacuum cleaner robot.""" def __init__(self, name, vacuum): diff --git a/tests/components/demo/test_vacuum.py b/tests/components/demo/test_vacuum.py index e64e7d178cd..5c9ac4205fe 100644 --- a/tests/components/demo/test_vacuum.py +++ b/tests/components/demo/test_vacuum.py @@ -228,7 +228,7 @@ async def test_unsupported_methods(hass): assert "spot" not in state.attributes.get(ATTR_STATUS) assert state.state == STATE_OFF - # VacuumDevice should not support start and pause methods. + # VacuumEntity should not support start and pause methods. hass.states.async_set(ENTITY_VACUUM_COMPLETE, STATE_ON) await hass.async_block_till_done() assert vacuum.is_on(hass, ENTITY_VACUUM_COMPLETE) @@ -243,7 +243,7 @@ async def test_unsupported_methods(hass): await common.async_start(hass, ENTITY_VACUUM_COMPLETE) assert not vacuum.is_on(hass, ENTITY_VACUUM_COMPLETE) - # StateVacuumDevice does not support on/off + # StateVacuumEntity does not support on/off await common.async_turn_on(hass, entity_id=ENTITY_VACUUM_STATE) state = hass.states.get(ENTITY_VACUUM_STATE) assert state.state != STATE_CLEANING diff --git a/tests/components/vacuum/test_init.py b/tests/components/vacuum/test_init.py new file mode 100644 index 00000000000..9075b385f40 --- /dev/null +++ b/tests/components/vacuum/test_init.py @@ -0,0 +1,18 @@ +"""The tests for Vacuum.""" +from homeassistant.components import vacuum + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomVacuum(vacuum.VacuumDevice): + pass + + class CustomStateVacuum(vacuum.StateVacuumDevice): + pass + + CustomVacuum() + assert "VacuumDevice is deprecated, modify CustomVacuum" in caplog.text + + CustomStateVacuum() + assert "StateVacuumDevice is deprecated, modify CustomStateVacuum" in caplog.text From aa60d362fd92769afe95825e4cb2776d05ce1a96 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 26 Apr 2020 02:12:36 +0200 Subject: [PATCH 071/511] Rename RemoteDevice to RemoteEntity (#34676) --- homeassistant/components/apple_tv/remote.py | 2 +- homeassistant/components/broadlink/remote.py | 4 ++-- homeassistant/components/demo/remote.py | 4 ++-- homeassistant/components/directv/remote.py | 4 ++-- homeassistant/components/harmony/remote.py | 2 +- homeassistant/components/itach/remote.py | 2 +- homeassistant/components/remote/__init__.py | 14 +++++++++++++- homeassistant/components/roku/remote.py | 4 ++-- homeassistant/components/xiaomi_miio/remote.py | 4 ++-- tests/components/remote/test_init.py | 10 ++++++++++ 10 files changed, 36 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/apple_tv/remote.py b/homeassistant/components/apple_tv/remote.py index dd784cc449d..4f935ba0ab8 100644 --- a/homeassistant/components/apple_tv/remote.py +++ b/homeassistant/components/apple_tv/remote.py @@ -17,7 +17,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([AppleTVRemote(atv, power, name)]) -class AppleTVRemote(remote.RemoteDevice): +class AppleTVRemote(remote.RemoteEntity): """Device that sends commands to an Apple TV.""" def __init__(self, atv, power, name): diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 177cf2ee0bd..364fa39cdb1 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -22,7 +22,7 @@ from homeassistant.components.remote import ( DOMAIN as COMPONENT, PLATFORM_SCHEMA, SUPPORT_LEARN_COMMAND, - RemoteDevice, + RemoteEntity, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE from homeassistant.core import callback @@ -124,7 +124,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([remote], False) -class BroadlinkRemote(RemoteDevice): +class BroadlinkRemote(RemoteEntity): """Representation of a Broadlink remote.""" def __init__(self, name, unique_id, api, code_storage, flag_storage): diff --git a/homeassistant/components/demo/remote.py b/homeassistant/components/demo/remote.py index 70e0d3c8b6e..9d12621fef1 100644 --- a/homeassistant/components/demo/remote.py +++ b/homeassistant/components/demo/remote.py @@ -1,5 +1,5 @@ """Demo platform that has two fake remotes.""" -from homeassistant.components.remote import RemoteDevice +from homeassistant.components.remote import RemoteEntity from homeassistant.const import DEVICE_DEFAULT_NAME @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): ) -class DemoRemote(RemoteDevice): +class DemoRemote(RemoteEntity): """Representation of a demo remote.""" def __init__(self, name, state, icon): diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index 8bc7c220833..776ce7a229b 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -5,7 +5,7 @@ from typing import Any, Callable, Iterable, List from directv import DIRECTV, DIRECTVError -from homeassistant.components.remote import RemoteDevice +from homeassistant.components.remote import RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -36,7 +36,7 @@ async def async_setup_entry( async_add_entities(entities, True) -class DIRECTVRemote(DIRECTVEntity, RemoteDevice): +class DIRECTVRemote(DIRECTVEntity, RemoteEntity): """Device that sends commands to a DirecTV receiver.""" def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None: diff --git a/homeassistant/components/harmony/remote.py b/homeassistant/components/harmony/remote.py index 147ee75a863..25b68b42e72 100644 --- a/homeassistant/components/harmony/remote.py +++ b/homeassistant/components/harmony/remote.py @@ -125,7 +125,7 @@ async def async_setup_entry( ) -class HarmonyRemote(remote.RemoteDevice): +class HarmonyRemote(remote.RemoteEntity): """Remote representation used to control a Harmony device.""" def __init__(self, name, unique_id, host, activity, out_path, delay_secs): diff --git a/homeassistant/components/itach/remote.py b/homeassistant/components/itach/remote.py index 5390111890c..2a1a2eac0ca 100644 --- a/homeassistant/components/itach/remote.py +++ b/homeassistant/components/itach/remote.py @@ -84,7 +84,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class ITachIP2IRRemote(remote.RemoteDevice): +class ITachIP2IRRemote(remote.RemoteEntity): """Device that sends commands to an ITachIP2IR device.""" def __init__(self, itachip2ir, name): diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 580c0a3b152..ca9de0bfb62 100644 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -122,7 +122,7 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return await cast(EntityComponent, hass.data[DOMAIN]).async_unload_entry(entry) -class RemoteDevice(ToggleEntity): +class RemoteEntity(ToggleEntity): """Representation of a remote.""" @property @@ -149,3 +149,15 @@ class RemoteDevice(ToggleEntity): """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)) + + +class RemoteDevice(RemoteEntity): + """Representation of a remote (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "RemoteDevice is deprecated, modify %s to extend RemoteEntity", + cls.__name__, + ) diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 999747c9a27..3a9adf3518c 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -7,7 +7,7 @@ from requests.exceptions import ( ) from roku import RokuException -from homeassistant.components.remote import RemoteDevice +from homeassistant.components.remote import RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -24,7 +24,7 @@ async def async_setup_entry( async_add_entities([RokuRemote(roku)], True) -class RokuRemote(RemoteDevice): +class RokuRemote(RemoteEntity): """Device that sends commands to an Roku.""" def __init__(self, roku): diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index 8c4d68208b4..fb188368127 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -12,7 +12,7 @@ from homeassistant.components.remote import ( ATTR_NUM_REPEATS, DEFAULT_DELAY_SECS, PLATFORM_SCHEMA, - RemoteDevice, + RemoteEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -165,7 +165,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class XiaomiMiioRemote(RemoteDevice): +class XiaomiMiioRemote(RemoteEntity): """Representation of a Xiaomi Miio Remote device.""" def __init__(self, friendly_name, device, unique_id, slot, timeout, commands): diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py index 24210a38d5c..031131276fe 100644 --- a/tests/components/remote/test_init.py +++ b/tests/components/remote/test_init.py @@ -117,3 +117,13 @@ class TestRemote(unittest.TestCase): assert call.domain == remote.DOMAIN assert call.service == SERVICE_LEARN_COMMAND assert call.data[ATTR_ENTITY_ID] == "entity_id_val" + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomRemote(remote.RemoteDevice): + pass + + CustomRemote() + assert "RemoteDevice is deprecated, modify CustomRemote" in caplog.text From f436e29a0df7769b18337b5003188a945857cc5b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 25 Apr 2020 17:30:15 -0700 Subject: [PATCH 072/511] Add frontend version WS command (#34701) --- homeassistant/components/frontend/__init__.py | 36 ++++++++++++------- tests/components/frontend/test_init.py | 22 ++++++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index f6a16205755..e5b93399c43 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -19,7 +19,7 @@ from homeassistant.core import callback from homeassistant.helpers import service import homeassistant.helpers.config_validation as cv from homeassistant.helpers.translation import async_get_translations -from homeassistant.loader import bind_hass +from homeassistant.loader import async_get_integration, bind_hass from .storage import async_setup_frontend_storage @@ -248,6 +248,7 @@ async def async_setup(hass, config): hass.components.websocket_api.async_register_command(websocket_get_panels) hass.components.websocket_api.async_register_command(websocket_get_themes) hass.components.websocket_api.async_register_command(websocket_get_translations) + hass.components.websocket_api.async_register_command(websocket_get_version) hass.http.register_view(ManifestJSONView) conf = config.get(DOMAIN, {}) @@ -486,10 +487,7 @@ class ManifestJSONView(HomeAssistantView): @callback @websocket_api.websocket_command({"type": "get_panels"}) def websocket_get_panels(hass, connection, msg): - """Handle get panels command. - - Async friendly. - """ + """Handle get panels command.""" user_is_admin = connection.user.is_admin panels = { panel_key: panel.to_response() @@ -503,10 +501,7 @@ def websocket_get_panels(hass, connection, msg): @callback @websocket_api.websocket_command({"type": "frontend/get_themes"}) def websocket_get_themes(hass, connection, msg): - """Handle get themes command. - - Async friendly. - """ + """Handle get themes command.""" if hass.config.safe_mode: connection.send_message( websocket_api.result_message( @@ -546,10 +541,7 @@ def websocket_get_themes(hass, connection, msg): ) @websocket_api.async_response async def websocket_get_translations(hass, connection, msg): - """Handle get translations command. - - Async friendly. - """ + """Handle get translations command.""" resources = await async_get_translations( hass, msg["language"], @@ -560,3 +552,21 @@ async def websocket_get_translations(hass, connection, msg): connection.send_message( websocket_api.result_message(msg["id"], {"resources": resources}) ) + + +@websocket_api.websocket_command({"type": "frontend/get_version"}) +@websocket_api.async_response +async def websocket_get_version(hass, connection, msg): + """Handle get version command.""" + integration = await async_get_integration(hass, "frontend") + + frontend = None + + for req in integration.requirements: + if req.startswith("home-assistant-frontend=="): + frontend = req.split("==", 1)[1] + + if frontend is None: + connection.send_error(msg["id"], "unknown_version", "Version not found") + else: + connection.send_result(msg["id"], {"version": frontend}) diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 7297812249c..ef254871830 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -14,6 +14,7 @@ from homeassistant.components.frontend import ( ) from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import HTTP_NOT_FOUND +from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component from tests.common import async_capture_events @@ -336,3 +337,24 @@ async def test_auth_authorize(mock_http_client): resp = await mock_http_client.get(authorizejs.groups(0)[0]) assert resp.status == 200 assert "public" in resp.headers.get("cache-control") + + +async def test_get_version(hass, hass_ws_client): + """Test get_version command.""" + frontend = await async_get_integration(hass, "frontend") + cur_version = next( + req.split("==", 1)[1] + for req in frontend.requirements + if req.startswith("home-assistant-frontend==") + ) + + await async_setup_component(hass, "frontend", {}) + client = await hass_ws_client(hass) + + await client.send_json({"id": 5, "type": "frontend/get_version"}) + msg = await client.receive_json() + + assert msg["id"] == 5 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + assert msg["result"] == {"version": cur_version} From 8ff1fc6f8b0d04b2ef885176b99cb2252fa86ec1 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 25 Apr 2020 21:30:12 -0600 Subject: [PATCH 073/511] Bump pyairvisual and remove unused trends (#34707) --- homeassistant/components/airvisual/__init__.py | 4 ++-- homeassistant/components/airvisual/air_quality.py | 12 ------------ homeassistant/components/airvisual/config_flow.py | 4 ++-- homeassistant/components/airvisual/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 7 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 73c39a450b8..4079f739824 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -169,7 +169,7 @@ async def async_setup_entry(hass, config_entry): _standardize_geography_config_entry(hass, config_entry) airvisual = AirVisualGeographyData( hass, - Client(websession, api_key=config_entry.data[CONF_API_KEY]), + Client(api_key=config_entry.data[CONF_API_KEY], session=websession), config_entry, ) @@ -177,7 +177,7 @@ async def async_setup_entry(hass, config_entry): config_entry.add_update_listener(async_update_options) else: _standardize_node_pro_config_entry(hass, config_entry) - airvisual = AirVisualNodeProData(hass, Client(websession), config_entry) + airvisual = AirVisualNodeProData(hass, Client(session=websession), config_entry) await airvisual.async_update() diff --git a/homeassistant/components/airvisual/air_quality.py b/homeassistant/components/airvisual/air_quality.py index 9da5b83d79f..71f9f9d9fbe 100644 --- a/homeassistant/components/airvisual/air_quality.py +++ b/homeassistant/components/airvisual/air_quality.py @@ -2,14 +2,12 @@ from homeassistant.components.air_quality import AirQualityEntity from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER from homeassistant.core import callback -from homeassistant.util import slugify from . import AirVisualEntity from .const import DATA_CLIENT, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY ATTR_HUMIDITY = "humidity" ATTR_SENSOR_LIFE = "{0}_sensor_life" -ATTR_TREND = "{0}_trend" ATTR_VOC = "voc" @@ -94,15 +92,6 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity): @callback def update_from_latest_data(self): """Update from the Node/Pro's data.""" - trends = { - ATTR_TREND.format(slugify(pollutant)): trend - for pollutant, trend in self._airvisual.data["trends"].items() - } - if self._airvisual.data["current"]["settings"]["is_aqi_usa"]: - trends.pop(ATTR_TREND.format("aqi_cn")) - else: - trends.pop(ATTR_TREND.format("aqi_us")) - self._attrs.update( { ATTR_VOC: self._airvisual.data["current"]["measurements"].get("voc"), @@ -112,6 +101,5 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity): "status" ]["sensor_life"].items() }, - **trends, } ) diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 22a8c776027..691fa19504a 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -101,7 +101,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_configured") websession = aiohttp_client.async_get_clientsession(self.hass) - client = Client(websession, api_key=user_input[CONF_API_KEY]) + client = Client(session=websession, api_key=user_input[CONF_API_KEY]) # If this is the first (and only the first) time we've seen this API key, check # that it's valid: @@ -142,7 +142,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) websession = aiohttp_client.async_get_clientsession(self.hass) - client = Client(websession) + client = Client(session=websession) try: await client.node.from_samba( diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index d97fcfb78ef..93b57a4804e 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -3,6 +3,6 @@ "name": "AirVisual", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airvisual", - "requirements": ["pyairvisual==4.3.0"], + "requirements": ["pyairvisual==4.4.0"], "codeowners": ["@bachya"] } diff --git a/requirements_all.txt b/requirements_all.txt index df8e3f15252..906c658a631 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1173,7 +1173,7 @@ pyaehw4a1==0.3.4 pyaftership==0.1.2 # homeassistant.components.airvisual -pyairvisual==4.3.0 +pyairvisual==4.4.0 # homeassistant.components.almond pyalmond==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 307b0de5b1e..57bdfc76ef0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -470,7 +470,7 @@ py_nextbusnext==0.1.4 pyaehw4a1==0.3.4 # homeassistant.components.airvisual -pyairvisual==4.3.0 +pyairvisual==4.4.0 # homeassistant.components.almond pyalmond==0.0.2 From 8402363568929908efa441e50062f13b513e8845 Mon Sep 17 00:00:00 2001 From: escoand Date: Sun, 26 Apr 2020 08:57:44 +0200 Subject: [PATCH 074/511] Fix fritzbox errors again (#34710) --- homeassistant/components/fritzbox/config_flow.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index ffcdb499c30..816855b46a8 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -42,8 +42,6 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): def __init__(self): """Initialize flow.""" self._host = None - self._manufacturer = None - self._model = None self._name = None self._password = None self._username = None @@ -83,7 +81,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_HOST) == user_input[CONF_HOST]: + if entry.data[CONF_HOST] == user_input[CONF_HOST]: if entry.data != user_input: self.hass.config_entries.async_update_entry( entry, data=user_input @@ -117,9 +115,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_in_progress") for entry in self.hass.config_entries.async_entries(DOMAIN): - if entry.data.get(CONF_HOST) == host: - if entry.data != user_input: - self.hass.config_entries.async_update_entry(entry, data=user_input) + if entry.data[CONF_HOST] == host: return self.async_abort(reason="already_configured") self._host = host From e4333a7a440f58721b4df7a2c828dd6fa168e750 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 26 Apr 2020 18:49:41 +0200 Subject: [PATCH 075/511] Rename Light to LightEntity (#34593) --- homeassistant/components/abode/light.py | 4 ++-- homeassistant/components/ads/light.py | 4 ++-- homeassistant/components/avea/light.py | 4 ++-- homeassistant/components/avion/light.py | 4 ++-- homeassistant/components/blinksticklight/light.py | 4 ++-- homeassistant/components/blinkt/light.py | 4 ++-- homeassistant/components/deconz/light.py | 4 ++-- homeassistant/components/decora/light.py | 4 ++-- homeassistant/components/decora_wifi/light.py | 4 ++-- homeassistant/components/demo/light.py | 4 ++-- homeassistant/components/dynalite/light.py | 4 ++-- homeassistant/components/elgato/light.py | 4 ++-- homeassistant/components/elkm1/light.py | 8 ++++++-- homeassistant/components/enocean/light.py | 4 ++-- homeassistant/components/esphome/light.py | 4 ++-- homeassistant/components/eufy/light.py | 4 ++-- homeassistant/components/everlights/light.py | 4 ++-- homeassistant/components/fibaro/light.py | 4 ++-- homeassistant/components/flux_led/light.py | 4 ++-- homeassistant/components/futurenow/light.py | 4 ++-- homeassistant/components/greenwave/light.py | 4 ++-- homeassistant/components/group/light.py | 2 +- homeassistant/components/hive/light.py | 4 ++-- .../components/homekit_controller/light.py | 4 ++-- homeassistant/components/homematic/light.py | 4 ++-- homeassistant/components/homematicip_cloud/light.py | 8 ++++---- homeassistant/components/homeworks/light.py | 8 ++++++-- homeassistant/components/hue/light.py | 4 ++-- homeassistant/components/hyperion/light.py | 4 ++-- homeassistant/components/iaqualink/light.py | 4 ++-- homeassistant/components/iglo/light.py | 4 ++-- homeassistant/components/ihc/light.py | 8 ++++++-- homeassistant/components/insteon/light.py | 8 ++++++-- homeassistant/components/isy994/light.py | 4 ++-- homeassistant/components/knx/light.py | 4 ++-- homeassistant/components/lcn/light.py | 6 +++--- homeassistant/components/lifx/light.py | 4 ++-- homeassistant/components/lifx_legacy/light.py | 4 ++-- homeassistant/components/light/__init__.py | 13 ++++++++++++- homeassistant/components/lightwave/light.py | 8 ++++++-- homeassistant/components/limitlessled/light.py | 4 ++-- homeassistant/components/litejet/light.py | 8 ++++++-- homeassistant/components/lutron/light.py | 8 ++++++-- homeassistant/components/lutron_caseta/light.py | 4 ++-- homeassistant/components/lw12wifi/light.py | 4 ++-- homeassistant/components/mochad/light.py | 4 ++-- homeassistant/components/mqtt/light/schema_basic.py | 6 +++--- homeassistant/components/mqtt/light/schema_json.py | 4 ++-- .../components/mqtt/light/schema_template.py | 4 ++-- homeassistant/components/mysensors/light.py | 4 ++-- homeassistant/components/mystrom/light.py | 4 ++-- homeassistant/components/nanoleaf/light.py | 4 ++-- homeassistant/components/niko_home_control/light.py | 4 ++-- homeassistant/components/opple/light.py | 4 ++-- homeassistant/components/osramlightify/light.py | 4 ++-- homeassistant/components/piglow/light.py | 4 ++-- homeassistant/components/pilight/light.py | 4 ++-- homeassistant/components/plum_lightpad/light.py | 6 +++--- homeassistant/components/qwikswitch/light.py | 4 ++-- homeassistant/components/rflink/light.py | 10 +++++----- homeassistant/components/rfxtrx/light.py | 4 ++-- homeassistant/components/ring/light.py | 4 ++-- homeassistant/components/scsgate/light.py | 4 ++-- homeassistant/components/sensehat/light.py | 4 ++-- homeassistant/components/sisyphus/light.py | 4 ++-- homeassistant/components/skybell/light.py | 4 ++-- homeassistant/components/smarthab/light.py | 4 ++-- homeassistant/components/smartthings/light.py | 4 ++-- homeassistant/components/switch/light.py | 4 ++-- homeassistant/components/tellduslive/light.py | 8 ++++++-- homeassistant/components/tellstick/light.py | 8 ++++++-- homeassistant/components/template/light.py | 4 ++-- homeassistant/components/tikteck/light.py | 4 ++-- homeassistant/components/tplink/light.py | 4 ++-- homeassistant/components/tradfri/light.py | 6 +++--- homeassistant/components/tuya/light.py | 4 ++-- homeassistant/components/unifiled/light.py | 4 ++-- homeassistant/components/velbus/light.py | 4 ++-- homeassistant/components/vera/light.py | 4 ++-- homeassistant/components/wemo/light.py | 6 +++--- homeassistant/components/wink/light.py | 4 ++-- homeassistant/components/wled/light.py | 4 ++-- homeassistant/components/x10/light.py | 4 ++-- homeassistant/components/xiaomi_aqara/light.py | 4 ++-- homeassistant/components/xiaomi_miio/light.py | 4 ++-- homeassistant/components/yeelight/light.py | 4 ++-- homeassistant/components/yeelightsunflower/light.py | 4 ++-- homeassistant/components/zengge/light.py | 4 ++-- homeassistant/components/zha/light.py | 2 +- homeassistant/components/zigbee/light.py | 4 ++-- homeassistant/components/zwave/light.py | 4 ++-- tests/components/light/test_init.py | 10 ++++++++++ .../testing_config/custom_components/test/light.py | 4 ++-- 93 files changed, 248 insertions(+), 191 deletions(-) diff --git a/homeassistant/components/abode/light.py b/homeassistant/components/abode/light.py index f15c10fc410..b756c79d9de 100644 --- a/homeassistant/components/abode/light.py +++ b/homeassistant/components/abode/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.util.color import ( color_temperature_kelvin_to_mired, @@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeLight(AbodeDevice, Light): +class AbodeLight(AbodeDevice, LightEntity): """Representation of an Abode light.""" def turn_on(self, **kwargs): diff --git a/homeassistant/components/ads/light.py b/homeassistant/components/ads/light.py index 384bd2e83a6..74701066078 100644 --- a/homeassistant/components/ads/light.py +++ b/homeassistant/components/ads/light.py @@ -7,7 +7,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([AdsLight(ads_hub, ads_var_enable, ads_var_brightness, name)]) -class AdsLight(AdsEntity, Light): +class AdsLight(AdsEntity, LightEntity): """Representation of ADS light.""" def __init__(self, ads_hub, ads_var_enable, ads_var_brightness, name): diff --git a/homeassistant/components/avea/light.py b/homeassistant/components/avea/light.py index 92d66a554da..8f57fb08e96 100644 --- a/homeassistant/components/avea/light.py +++ b/homeassistant/components/avea/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.exceptions import PlatformNotReady import homeassistant.util.color as color_util @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(AveaLight(bulb) for bulb in nearby_bulbs) -class AveaLight(Light): +class AveaLight(LightEntity): """Representation of an Avea.""" def __init__(self, light): diff --git a/homeassistant/components/avion/light.py b/homeassistant/components/avion/light.py index 0c95b2bf736..e5281c13654 100644 --- a/homeassistant/components/avion/light.py +++ b/homeassistant/components/avion/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import ( CONF_API_KEY, @@ -66,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(lights) -class AvionLight(Light): +class AvionLight(LightEntity): """Representation of an Avion light.""" def __init__(self, device): diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py index 4eab2fc3d11..56009c90da3 100644 --- a/homeassistant/components/blinksticklight/light.py +++ b/homeassistant/components/blinksticklight/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([BlinkStickLight(stick, name)], True) -class BlinkStickLight(Light): +class BlinkStickLight(LightEntity): """Representation of a BlinkStick light.""" def __init__(self, stick, name): diff --git a/homeassistant/components/blinkt/light.py b/homeassistant/components/blinkt/light.py index d9ef2ac6a7e..768ca92d9d2 100644 --- a/homeassistant/components/blinkt/light.py +++ b/homeassistant/components/blinkt/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class BlinktLight(Light): +class BlinktLight(LightEntity): """Representation of a Blinkt! Light.""" def __init__(self, blinkt, name, index): diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index e836f1e4490..48d286266e4 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -82,7 +82,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_group(gateway.api.groups.values()) -class DeconzLight(DeconzDevice, Light): +class DeconzLight(DeconzDevice, LightEntity): """Representation of a deCONZ light.""" def __init__(self, device, gateway): diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index f4035352e51..5b6015b7c54 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -12,7 +12,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_API_KEY, CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -84,7 +84,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(lights) -class DecoraLight(Light): +class DecoraLight(LightEntity): """Representation of an Decora light.""" def __init__(self, device): diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 9071da9707d..6f716d3a5dc 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv @@ -80,7 +80,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout) -class DecoraWifiLight(Light): +class DecoraWifiLight(LightEntity): """Representation of a Decora WiFi switch.""" def __init__(self, switch): diff --git a/homeassistant/components/demo/light.py b/homeassistant/components/demo/light.py index e6747fee2df..11b6a4812e8 100644 --- a/homeassistant/components/demo/light.py +++ b/homeassistant/components/demo/light.py @@ -12,7 +12,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from . import DOMAIN @@ -57,7 +57,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoLight(Light): +class DemoLight(LightEntity): """Representation of a demo light.""" def __init__( diff --git a/homeassistant/components/dynalite/light.py b/homeassistant/components/dynalite/light.py index 283b1ee2286..5e7069ab50b 100644 --- a/homeassistant/components/dynalite/light.py +++ b/homeassistant/components/dynalite/light.py @@ -1,7 +1,7 @@ """Support for Dynalite channels as lights.""" from typing import Callable -from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -18,7 +18,7 @@ async def async_setup_entry( ) -class DynaliteLight(DynaliteBase, Light): +class DynaliteLight(DynaliteBase, LightEntity): """Representation of a Dynalite Channel as a Home Assistant Light.""" @property diff --git a/homeassistant/components/elgato/light.py b/homeassistant/components/elgato/light.py index 99bca1ba20e..9dae0cd1f40 100644 --- a/homeassistant/components/elgato/light.py +++ b/homeassistant/components/elgato/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( ATTR_COLOR_TEMP, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_NAME @@ -45,7 +45,7 @@ async def async_setup_entry( async_add_entities([ElgatoLight(entry.entry_id, elgato, info)], True) -class ElgatoLight(Light): +class ElgatoLight(LightEntity): """Defines a Elgato Key Light.""" def __init__( diff --git a/homeassistant/components/elkm1/light.py b/homeassistant/components/elkm1/light.py index b7cfe20dfd8..19a09d13975 100644 --- a/homeassistant/components/elkm1/light.py +++ b/homeassistant/components/elkm1/light.py @@ -1,6 +1,10 @@ """Support for control of ElkM1 lighting (X10, UPB, etc).""" -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from . import ElkEntity, create_elk_entities from .const import DOMAIN @@ -15,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class ElkLight(ElkEntity, Light): +class ElkLight(ElkEntity, LightEntity): """Representation of an Elk lighting device.""" def __init__(self, element, elk, elk_data): diff --git a/homeassistant/components/enocean/light.py b/homeassistant/components/enocean/light.py index a1d2b22cdb4..0df0c94775a 100644 --- a/homeassistant/components/enocean/light.py +++ b/homeassistant/components/enocean/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanLight(sender_id, dev_id, dev_name)]) -class EnOceanLight(enocean.EnOceanDevice, Light): +class EnOceanLight(enocean.EnOceanDevice, LightEntity): """Representation of an EnOcean light source.""" def __init__(self, sender_id, dev_id, dev_name): diff --git a/homeassistant/components/esphome/light.py b/homeassistant/components/esphome/light.py index 9a2a0ccd0bc..36c22f28016 100644 --- a/homeassistant/components/esphome/light.py +++ b/homeassistant/components/esphome/light.py @@ -21,7 +21,7 @@ from homeassistant.components.light import ( SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -50,7 +50,7 @@ async def async_setup_entry( ) -class EsphomeLight(EsphomeEntity, Light): +class EsphomeLight(EsphomeEntity, LightEntity): """A switch implementation for ESPHome.""" @property diff --git a/homeassistant/components/eufy/light.py b/homeassistant/components/eufy/light.py index 570f690307f..2c23eca483f 100644 --- a/homeassistant/components/eufy/light.py +++ b/homeassistant/components/eufy/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) import homeassistant.util.color as color_util from homeassistant.util.color import ( @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EufyLight(discovery_info)], True) -class EufyLight(Light): +class EufyLight(LightEntity): """Representation of a Eufy light.""" def __init__(self, device): diff --git a/homeassistant/components/everlights/light.py b/homeassistant/components/everlights/light.py index da9d5b88ae0..95571a825b2 100644 --- a/homeassistant/components/everlights/light.py +++ b/homeassistant/components/everlights/light.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, - Light, + LightEntity, ) from homeassistant.const import CONF_HOSTS from homeassistant.exceptions import PlatformNotReady @@ -65,7 +65,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(lights) -class EverLightsLight(Light): +class EverLightsLight(LightEntity): """Representation of a Flux light.""" def __init__(self, api, channel, status, effects): diff --git a/homeassistant/components/fibaro/light.py b/homeassistant/components/fibaro/light.py index d14d9a195d9..f73347cf356 100644 --- a/homeassistant/components/fibaro/light.py +++ b/homeassistant/components/fibaro/light.py @@ -11,7 +11,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import CONF_WHITE_VALUE import homeassistant.util.color as color_util @@ -48,7 +48,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class FibaroLight(FibaroDevice, Light): +class FibaroLight(FibaroDevice, LightEntity): """Representation of a Fibaro Light, including dimmable.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/flux_led/light.py b/homeassistant/components/flux_led/light.py index 1acd58d8e43..4bfd0c0a26c 100644 --- a/homeassistant/components/flux_led/light.py +++ b/homeassistant/components/flux_led/light.py @@ -19,7 +19,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import ATTR_MODE, CONF_DEVICES, CONF_NAME, CONF_PROTOCOL import homeassistant.helpers.config_validation as cv @@ -176,7 +176,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(lights, True) -class FluxLight(Light): +class FluxLight(LightEntity): """Representation of a Flux light.""" def __init__(self, device): diff --git a/homeassistant/components/futurenow/light.py b/homeassistant/components/futurenow/light.py index 4da3bfd5bc3..2bccd38688a 100644 --- a/homeassistant/components/futurenow/light.py +++ b/homeassistant/components/futurenow/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -64,7 +64,7 @@ def to_hass_level(level): return int((level * 255) / 100) -class FutureNowLight(Light): +class FutureNowLight(LightEntity): """Representation of an FutureNow light.""" def __init__(self, device): diff --git a/homeassistant/components/greenwave/light.py b/homeassistant/components/greenwave/light.py index 8b85de598b0..41e4b99b6c6 100644 --- a/homeassistant/components/greenwave/light.py +++ b/homeassistant/components/greenwave/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv @@ -54,7 +54,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class GreenwaveLight(Light): +class GreenwaveLight(LightEntity): """Representation of an Greenwave Reality Light.""" def __init__(self, light, host, token, gatewaydata): diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 3abca98bd2c..eb043177eba 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -76,7 +76,7 @@ async def async_setup_platform( ) -class LightGroup(light.Light): +class LightGroup(light.LightEntity): """Representation of a light group.""" def __init__(self, name: str, entity_ids: List[str]) -> None: diff --git a/homeassistant/components/hive/light.py b/homeassistant/components/hive/light.py index 33175de543d..d6a9d1f400b 100644 --- a/homeassistant/components/hive/light.py +++ b/homeassistant/components/hive/light.py @@ -6,7 +6,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class HiveDeviceLight(HiveEntity, Light): +class HiveDeviceLight(HiveEntity, LightEntity): """Hive Active Light Device.""" def __init__(self, hive_session, hive_device): diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index e78ed48ad0c..b024efe6121 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.core import callback @@ -35,7 +35,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): conn.add_listener(async_add_service) -class HomeKitLight(HomeKitEntity, Light): +class HomeKitLight(HomeKitEntity, LightEntity): """Representation of a Homekit light.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index 6e6ccb78371..c7cfcc2ac8c 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -11,7 +11,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - Light, + LightEntity, ) from .const import ATTR_DISCOVER_DEVICES @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMLight(HMDevice, Light): +class HMLight(HMDevice, LightEntity): """Representation of a Homematic light.""" @property diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 42c18239ac2..9ddcc44e8bd 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -21,7 +21,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -64,7 +64,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipLight(HomematicipGenericDevice, Light): +class HomematicipLight(HomematicipGenericDevice, LightEntity): """Representation of a HomematicIP Cloud light device.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -110,7 +110,7 @@ class HomematicipLightMeasuring(HomematicipLight): return state_attr -class HomematicipDimmer(HomematicipGenericDevice, Light): +class HomematicipDimmer(HomematicipGenericDevice, LightEntity): """Representation of HomematicIP Cloud dimmer light device.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -144,7 +144,7 @@ class HomematicipDimmer(HomematicipGenericDevice, Light): await self._device.set_dim_level(0) -class HomematicipNotificationLight(HomematicipGenericDevice, Light): +class HomematicipNotificationLight(HomematicipGenericDevice, LightEntity): """Representation of HomematicIP Cloud dimmer light device.""" def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: diff --git a/homeassistant/components/homeworks/light.py b/homeassistant/components/homeworks/light.py index db72c87a4a3..a5a3b9ed077 100644 --- a/homeassistant/components/homeworks/light.py +++ b/homeassistant/components/homeworks/light.py @@ -3,7 +3,11 @@ import logging from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from homeassistant.const import CONF_NAME from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -28,7 +32,7 @@ def setup_platform(hass, config, add_entities, discover_info=None): add_entities(devs, True) -class HomeworksLight(HomeworksDevice, Light): +class HomeworksLight(HomeworksDevice, LightEntity): """Homeworks Light.""" def __init__(self, controller, addr, name, rate): diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index b808dd0594d..7b5b7e6e804 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -24,7 +24,7 @@ from homeassistant.components.light import ( SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.core import callback from homeassistant.exceptions import PlatformNotReady @@ -194,7 +194,7 @@ def hass_to_hue_brightness(value): return max(1, round((value / 255) * 254)) -class HueLight(Light): +class HueLight(LightEntity): """Representation of a Hue light.""" def __init__(self, coordinator, bridge, is_group, light, supported_features): diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index fc96f672afb..d1baec315bf 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -13,7 +13,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_EFFECT, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -103,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([device]) -class Hyperion(Light): +class Hyperion(LightEntity): """Representation of a Hyperion remote.""" def __init__( diff --git a/homeassistant/components/iaqualink/light.py b/homeassistant/components/iaqualink/light.py index 813af7863f1..362ef8b8b8e 100644 --- a/homeassistant/components/iaqualink/light.py +++ b/homeassistant/components/iaqualink/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( DOMAIN, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -32,7 +32,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkLight(AqualinkEntity, Light): +class HassAqualinkLight(AqualinkEntity, LightEntity): """Representation of a light.""" @property diff --git a/homeassistant/components/iglo/light.py b/homeassistant/components/iglo/light.py index 59e6db2a81f..f6f681d8b60 100644 --- a/homeassistant/components/iglo/light.py +++ b/homeassistant/components/iglo/light.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([IGloLamp(name, host, port)], True) -class IGloLamp(Light): +class IGloLamp(LightEntity): """Representation of an iGlo light.""" def __init__(self, name, host, port): diff --git a/homeassistant/components/ihc/light.py b/homeassistant/components/ihc/light.py index af6b62c42ff..c35cb2224cf 100644 --- a/homeassistant/components/ihc/light.py +++ b/homeassistant/components/ihc/light.py @@ -1,7 +1,11 @@ """Support for IHC lights.""" import logging -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from . import IHC_CONTROLLER, IHC_INFO from .const import CONF_DIMMABLE, CONF_OFF_ID, CONF_ON_ID @@ -35,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class IhcLight(IHCDevice, Light): +class IhcLight(IHCDevice, LightEntity): """Representation of a IHC light. For dimmable lights, the associated IHC resource should be a light diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index 60a27b3acb8..afd575c363b 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -1,7 +1,11 @@ """Support for Insteon lights via PowerLinc Modem.""" import logging -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from .insteon_entity import InsteonEntity @@ -29,7 +33,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([new_entity]) -class InsteonDimmerDevice(InsteonEntity, Light): +class InsteonDimmerDevice(InsteonEntity, LightEntity): """A Class for an Insteon device.""" @property diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index a8c30220637..0d66a73571d 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -2,7 +2,7 @@ import logging from typing import Callable -from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS, LightEntity from homeassistant.helpers.typing import ConfigType from . import ISY994_NODES, ISYDevice @@ -21,7 +21,7 @@ def setup_platform( add_entities(devices) -class ISYLightDevice(ISYDevice, Light): +class ISYLightDevice(ISYDevice, LightEntity): """Representation of an ISY994 light device.""" def __init__(self, node) -> None: diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 8570c2eb09a..bb82e86cd25 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.core import callback @@ -132,7 +132,7 @@ def async_add_entities_config(hass, config, async_add_entities): async_add_entities([KNXLight(light)]) -class KNXLight(Light): +class KNXLight(LightEntity): """Representation of a KNX light.""" def __init__(self, device): diff --git a/homeassistant/components/lcn/light.py b/homeassistant/components/lcn/light.py index 7f1cd547c02..e76becc0e9f 100644 --- a/homeassistant/components/lcn/light.py +++ b/homeassistant/components/lcn/light.py @@ -6,7 +6,7 @@ from homeassistant.components.light import ( ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_ADDRESS @@ -47,7 +47,7 @@ async def async_setup_platform( async_add_entities(devices) -class LcnOutputLight(LcnDevice, Light): +class LcnOutputLight(LcnDevice, LightEntity): """Representation of a LCN light for output ports.""" def __init__(self, config, address_connection): @@ -135,7 +135,7 @@ class LcnOutputLight(LcnDevice, Light): self.async_write_ha_state() -class LcnRelayLight(LcnDevice, Light): +class LcnRelayLight(LcnDevice, LightEntity): """Representation of a LCN light for relay ports.""" def __init__(self, config, address_connection): diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index ca04dbefb7f..f36b64f2397 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -32,7 +32,7 @@ from homeassistant.components.light import ( SUPPORT_TRANSITION, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, - Light, + LightEntity, preprocess_turn_on_alternatives, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, EVENT_HOMEASSISTANT_STOP @@ -438,7 +438,7 @@ def convert_16_to_8(value): return value >> 8 -class LIFXLight(Light): +class LIFXLight(LightEntity): """Representation of a LIFX light.""" def __init__(self, bulb, effects_conductor): diff --git a/homeassistant/components/lifx_legacy/light.py b/homeassistant/components/lifx_legacy/light.py index 7fb0e686b31..f0ed9105b99 100644 --- a/homeassistant/components/lifx_legacy/light.py +++ b/homeassistant/components/lifx_legacy/light.py @@ -19,7 +19,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, - Light, + LightEntity, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_time_change @@ -137,7 +137,7 @@ class LIFX: self._liffylights.probe(address) -class LIFXLight(Light): +class LIFXLight(LightEntity): """Representation of a LIFX light.""" def __init__(self, liffy, ipaddr, name, power, hue, saturation, brightness, kelvin): diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 7c33fdcb075..d25d6b961ed 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -332,7 +332,7 @@ class Profiles: return None -class Light(ToggleEntity): +class LightEntity(ToggleEntity): """Representation of a light.""" @property @@ -428,3 +428,14 @@ class Light(ToggleEntity): def supported_features(self): """Flag supported features.""" return 0 + + +class Light(LightEntity): + """Representation of a light (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "Light is deprecated, modify %s to extend LightEntity", cls.__name__, + ) diff --git a/homeassistant/components/lightwave/light.py b/homeassistant/components/lightwave/light.py index 78e4c43a0e7..d441e80b4fa 100644 --- a/homeassistant/components/lightwave/light.py +++ b/homeassistant/components/lightwave/light.py @@ -1,5 +1,9 @@ """Support for LightwaveRF lights.""" -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from homeassistant.const import CONF_NAME from . import LIGHTWAVE_LINK @@ -22,7 +26,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(lights) -class LWRFLight(Light): +class LWRFLight(LightEntity): """Representation of a LightWaveRF light.""" def __init__(self, name, device_id, lwlink): diff --git a/homeassistant/components/limitlessled/light.py b/homeassistant/components/limitlessled/light.py index e0ef635ae87..682d619a92c 100644 --- a/homeassistant/components/limitlessled/light.py +++ b/homeassistant/components/limitlessled/light.py @@ -28,7 +28,7 @@ from homeassistant.components.light import ( SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_ON import homeassistant.helpers.config_validation as cv @@ -200,7 +200,7 @@ def state(new_state): return decorator -class LimitlessLEDGroup(Light, RestoreEntity): +class LimitlessLEDGroup(LightEntity, RestoreEntity): """Representation of a LimitessLED group.""" def __init__(self, group, config): diff --git a/homeassistant/components/litejet/light.py b/homeassistant/components/litejet/light.py index d99cf27852f..efc6830d775 100644 --- a/homeassistant/components/litejet/light.py +++ b/homeassistant/components/litejet/light.py @@ -2,7 +2,11 @@ import logging from homeassistant.components import litejet -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) _LOGGER = logging.getLogger(__name__) @@ -21,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class LiteJetLight(Light): +class LiteJetLight(LightEntity): """Representation of a single LiteJet light.""" def __init__(self, hass, lj, i, name): diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 938132259d9..2b5bff7d848 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -1,7 +1,11 @@ """Support for Lutron lights.""" import logging -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice @@ -28,7 +32,7 @@ def to_hass_level(level): return int((level * 255) / 100) -class LutronLight(LutronDevice, Light): +class LutronLight(LutronDevice, LightEntity): """Representation of a Lutron Light, including dimmable.""" def __init__(self, area_name, lutron_device, controller): diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index 350c35fffa8..f6ec0369509 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -5,7 +5,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, DOMAIN, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice @@ -35,7 +35,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities, True) -class LutronCasetaLight(LutronCasetaDevice, Light): +class LutronCasetaLight(LutronCasetaDevice, LightEntity): """Representation of a Lutron Light, including dimmable.""" @property diff --git a/homeassistant/components/lw12wifi/light.py b/homeassistant/components/lw12wifi/light.py index abf75a1e318..907e6b898d6 100644 --- a/homeassistant/components/lw12wifi/light.py +++ b/homeassistant/components/lw12wifi/light.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_EFFECT, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([LW12WiFi(name, lw12_light)]) -class LW12WiFi(Light): +class LW12WiFi(LightEntity): """LW-12 WiFi LED Controller.""" def __init__(self, name, lw12_light): diff --git a/homeassistant/components/mochad/light.py b/homeassistant/components/mochad/light.py index 1f5cbc6bb95..a2264a65b16 100644 --- a/homeassistant/components/mochad/light.py +++ b/homeassistant/components/mochad/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import config_validation as cv @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class MochadLight(Light): +class MochadLight(LightEntity): """Representation of a X10 dimmer over Mochad.""" def __init__(self, hass, ctrl, dev): diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index e6a7f827f1e..2af63311e47 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.components.mqtt import ( CONF_COMMAND_TOPIC, @@ -86,7 +86,7 @@ CONF_WHITE_VALUE_TEMPLATE = "white_value_template" CONF_ON_COMMAND_TYPE = "on_command_type" DEFAULT_BRIGHTNESS_SCALE = 255 -DEFAULT_NAME = "MQTT Light" +DEFAULT_NAME = "MQTT LightEntity" DEFAULT_OPTIMISTIC = False DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_ON = "ON" @@ -160,7 +160,7 @@ class MqttLight( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - Light, + LightEntity, RestoreEntity, ): """Representation of a MQTT light.""" diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 924559e3e90..ef311cbe8a7 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -22,7 +22,7 @@ from homeassistant.components.light import ( SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.components.mqtt import ( CONF_COMMAND_TOPIC, @@ -131,7 +131,7 @@ class MqttLightJson( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - Light, + LightEntity, RestoreEntity, ): """Representation of a MQTT JSON light.""" diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 25de6862bdb..fab6db4906f 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -19,7 +19,7 @@ from homeassistant.components.light import ( SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.components.mqtt import ( CONF_COMMAND_TOPIC, @@ -105,7 +105,7 @@ class MqttTemplate( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - Light, + LightEntity, RestoreEntity, ): """Representation of a MQTT Template light.""" diff --git a/homeassistant/components/mysensors/light.py b/homeassistant/components/mysensors/light.py index 1585de4b462..ffbcba6f032 100644 --- a/homeassistant/components/mysensors/light.py +++ b/homeassistant/components/mysensors/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback @@ -34,7 +34,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MySensorsLight(mysensors.device.MySensorsEntity, Light): +class MySensorsLight(mysensors.device.MySensorsEntity, LightEntity): """Representation of a MySensors Light child node.""" def __init__(self, *args): diff --git a/homeassistant/components/mystrom/light.py b/homeassistant/components/mystrom/light.py index 72ec051b120..2762792e133 100644 --- a/homeassistant/components/mystrom/light.py +++ b/homeassistant/components/mystrom/light.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_EFFECT, SUPPORT_FLASH, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.exceptions import PlatformNotReady @@ -59,7 +59,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([MyStromLight(bulb, name, mac)], True) -class MyStromLight(Light): +class MyStromLight(LightEntity): """Representation of the myStrom WiFi bulb.""" def __init__(self, bulb, name, mac): diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 612e1b6ead9..5073a421e49 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -16,7 +16,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv @@ -103,7 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([NanoleafLight(nanoleaf_light, name)], True) -class NanoleafLight(Light): +class NanoleafLight(LightEntity): """Representation of a Nanoleaf Light.""" def __init__(self, light, name): diff --git a/homeassistant/components/niko_home_control/light.py b/homeassistant/components/niko_home_control/light.py index 265e51d6e67..4875e2e1e57 100644 --- a/homeassistant/components/niko_home_control/light.py +++ b/homeassistant/components/niko_home_control/light.py @@ -6,7 +6,7 @@ import nikohomecontrol import voluptuous as vol # Import the device class from the component that you want to support -from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, Light +from homeassistant.components.light import ATTR_BRIGHTNESS, PLATFORM_SCHEMA, LightEntity from homeassistant.const import CONF_HOST from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -38,7 +38,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class NikoHomeControlLight(Light): +class NikoHomeControlLight(LightEntity): """Representation of an Niko Light.""" def __init__(self, light, data): diff --git a/homeassistant/components/opple/light.py b/homeassistant/components/opple/light.py index 9ee53704d10..bd0f40da20b 100644 --- a/homeassistant/components/opple/light.py +++ b/homeassistant/components/opple/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug("Init light %s %s", host, entity.unique_id) -class OppleLight(Light): +class OppleLight(LightEntity): """Opple light device.""" def __init__(self, name, host): diff --git a/homeassistant/components/osramlightify/light.py b/homeassistant/components/osramlightify/light.py index ed79604a3f8..49c32da69bc 100644 --- a/homeassistant/components/osramlightify/light.py +++ b/homeassistant/components/osramlightify/light.py @@ -18,7 +18,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv @@ -168,7 +168,7 @@ def setup_bridge(bridge, add_entities, config): update_groups() -class Luminary(Light): +class Luminary(LightEntity): """Representation of Luminary Lights and Groups.""" def __init__(self, luminary, update_func, changed): diff --git a/homeassistant/components/piglow/light.py b/homeassistant/components/piglow/light.py index 27bbb81d31f..1e6040f9d80 100644 --- a/homeassistant/components/piglow/light.py +++ b/homeassistant/components/piglow/light.py @@ -11,7 +11,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([PiglowLight(name)]) -class PiglowLight(Light): +class PiglowLight(LightEntity): """Representation of an Piglow Light.""" def __init__(self, name): diff --git a/homeassistant/components/pilight/light.py b/homeassistant/components/pilight/light.py index 49ce3d9a124..12d175817d7 100644 --- a/homeassistant/components/pilight/light.py +++ b/homeassistant/components/pilight/light.py @@ -7,7 +7,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_LIGHTS import homeassistant.helpers.config_validation as cv @@ -40,7 +40,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class PilightLight(PilightBaseDevice, Light): +class PilightLight(PilightBaseDevice, LightEntity): """Representation of a Pilight switch.""" def __init__(self, hass, name, config): diff --git a/homeassistant/components/plum_lightpad/light.py b/homeassistant/components/plum_lightpad/light.py index 1ce76d9dc5f..737c6f2bfad 100644 --- a/homeassistant/components/plum_lightpad/light.py +++ b/homeassistant/components/plum_lightpad/light.py @@ -4,7 +4,7 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -32,7 +32,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(entities) -class PlumLight(Light): +class PlumLight(LightEntity): """Representation of a Plum Lightpad dimmer.""" def __init__(self, load): @@ -88,7 +88,7 @@ class PlumLight(Light): await self._load.turn_off() -class GlowRing(Light): +class GlowRing(LightEntity): """Representation of a Plum Lightpad dimmer glow ring.""" def __init__(self, lightpad): diff --git a/homeassistant/components/qwikswitch/light.py b/homeassistant/components/qwikswitch/light.py index 1adcef56ffa..43c6add6048 100644 --- a/homeassistant/components/qwikswitch/light.py +++ b/homeassistant/components/qwikswitch/light.py @@ -1,5 +1,5 @@ """Support for Qwikswitch Relays and Dimmers.""" -from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity from . import DOMAIN as QWIKSWITCH, QSToggleEntity @@ -14,7 +14,7 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): add_entities(devs) -class QSLight(QSToggleEntity, Light): +class QSLight(QSToggleEntity, LightEntity): """Light based on a Qwikswitch relay/dimmer module.""" @property diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 348fff0da9a..65d6acb3de9 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -7,7 +7,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME, CONF_TYPE import homeassistant.helpers.config_validation as cv @@ -159,11 +159,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_COMMAND] = add_new_device -class RflinkLight(SwitchableRflinkDevice, Light): +class RflinkLight(SwitchableRflinkDevice, LightEntity): """Representation of a Rflink light.""" -class DimmableRflinkLight(SwitchableRflinkDevice, Light): +class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity): """Rflink light device that support dimming.""" _brightness = 255 @@ -208,7 +208,7 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light): return SUPPORT_BRIGHTNESS -class HybridRflinkLight(SwitchableRflinkDevice, Light): +class HybridRflinkLight(SwitchableRflinkDevice, 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 @@ -271,7 +271,7 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light): return SUPPORT_BRIGHTNESS -class ToggleRflinkLight(SwitchableRflinkDevice, Light): +class ToggleRflinkLight(SwitchableRflinkDevice, LightEntity): """Rflink light device which sends out only 'on' commands. Some switches like for example Livolo light switches use the diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 437cce89c49..ea6c834f63b 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME, STATE_ON from homeassistant.helpers import config_validation as cv @@ -73,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(light_update) -class RfxtrxLight(RfxtrxDevice, Light, RestoreEntity): +class RfxtrxLight(RfxtrxDevice, LightEntity, RestoreEntity): """Representation of a RFXtrx light.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py index 3340e0ad603..8a5b75cfecd 100644 --- a/homeassistant/components/ring/light.py +++ b/homeassistant/components/ring/light.py @@ -4,7 +4,7 @@ import logging import requests -from homeassistant.components.light import Light +from homeassistant.components.light import LightEntity from homeassistant.core import callback import homeassistant.util.dt as dt_util @@ -38,7 +38,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(lights) -class RingLight(RingEntityMixin, Light): +class RingLight(RingEntityMixin, LightEntity): """Creates a switch to turn the ring cameras light on and off.""" def __init__(self, config_entry_id, device): diff --git a/homeassistant/components/scsgate/light.py b/homeassistant/components/scsgate/light.py index 8caa824d5fb..c8f5b720c70 100644 --- a/homeassistant/components/scsgate/light.py +++ b/homeassistant/components/scsgate/light.py @@ -4,7 +4,7 @@ import logging from scsgate.tasks import ToggleStatusTask import voluptuous as vol -from homeassistant.components.light import PLATFORM_SCHEMA, Light +from homeassistant.components.light import PLATFORM_SCHEMA, LightEntity from homeassistant.const import ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): scsgate.add_devices_to_register(lights) -class SCSGateLight(Light): +class SCSGateLight(LightEntity): """Representation of a SCSGate light.""" def __init__(self, scs_id, name, logger, scsgate): diff --git a/homeassistant/components/sensehat/light.py b/homeassistant/components/sensehat/light.py index 462c4245cd4..5eda783bebf 100644 --- a/homeassistant/components/sensehat/light.py +++ b/homeassistant/components/sensehat/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SenseHatLight(sensehat, name)]) -class SenseHatLight(Light): +class SenseHatLight(LightEntity): """Representation of an Sense Hat Light.""" def __init__(self, sensehat, name): diff --git a/homeassistant/components/sisyphus/light.py b/homeassistant/components/sisyphus/light.py index 271db41ac22..ce0c37174ef 100644 --- a/homeassistant/components/sisyphus/light.py +++ b/homeassistant/components/sisyphus/light.py @@ -3,7 +3,7 @@ import logging import aiohttp -from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import SUPPORT_BRIGHTNESS, LightEntity from homeassistant.const import CONF_HOST from homeassistant.exceptions import PlatformNotReady @@ -26,7 +26,7 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SisyphusLight(table_holder.name, table)], update_before_add=True) -class SisyphusLight(Light): +class SisyphusLight(LightEntity): """Representation of a Sisyphus table as a light.""" def __init__(self, name, table): diff --git a/homeassistant/components/skybell/light.py b/homeassistant/components/skybell/light.py index c9aa622ad0b..273cdfd079c 100644 --- a/homeassistant/components/skybell/light.py +++ b/homeassistant/components/skybell/light.py @@ -6,7 +6,7 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -36,7 +36,7 @@ def _to_hass_level(level): return int((level * 255) / 100) -class SkybellLight(SkybellDevice, Light): +class SkybellLight(SkybellDevice, LightEntity): """A binary sensor implementation for Skybell devices.""" def __init__(self, device): diff --git a/homeassistant/components/smarthab/light.py b/homeassistant/components/smarthab/light.py index 469d89011b8..8b608cfbd4f 100644 --- a/homeassistant/components/smarthab/light.py +++ b/homeassistant/components/smarthab/light.py @@ -5,7 +5,7 @@ import logging import pysmarthab from requests.exceptions import Timeout -from homeassistant.components.light import Light +from homeassistant.components.light import LightEntity from . import DATA_HUB, DOMAIN @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class SmartHabLight(Light): +class SmartHabLight(LightEntity): """Representation of a SmartHab Light.""" def __init__(self, light): diff --git a/homeassistant/components/smartthings/light.py b/homeassistant/components/smartthings/light.py index 7978d85505d..1e4161abd0f 100644 --- a/homeassistant/components/smartthings/light.py +++ b/homeassistant/components/smartthings/light.py @@ -13,7 +13,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -61,7 +61,7 @@ def convert_scale(value, value_scale, target_scale, round_digits=4): return round(value * target_scale / value_scale, round_digits) -class SmartThingsLight(SmartThingsEntity, Light): +class SmartThingsLight(SmartThingsEntity, LightEntity): """Define a SmartThings Light.""" def __init__(self, device): diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 92b64b36b93..f40ccde5b0b 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -5,7 +5,7 @@ from typing import Callable, Optional, Sequence, cast import voluptuous as vol from homeassistant.components import switch -from homeassistant.components.light import PLATFORM_SCHEMA, Light +from homeassistant.components.light import PLATFORM_SCHEMA, LightEntity from homeassistant.const import ( ATTR_ENTITY_ID, CONF_ENTITY_ID, @@ -49,7 +49,7 @@ async def async_setup_platform( ) -class LightSwitch(Light): +class LightSwitch(LightEntity): """Represents a Switch as a Light.""" def __init__(self, name: str, switch_entity_id: str) -> None: diff --git a/homeassistant/components/tellduslive/light.py b/homeassistant/components/tellduslive/light.py index 3087c4cdf08..76d26a2fd94 100644 --- a/homeassistant/components/tellduslive/light.py +++ b/homeassistant/components/tellduslive/light.py @@ -2,7 +2,11 @@ import logging from homeassistant.components import light, tellduslive -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect from .entry import TelldusLiveEntity @@ -25,7 +29,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TelldusLiveLight(TelldusLiveEntity, Light): +class TelldusLiveLight(TelldusLiveEntity, LightEntity): """Representation of a Tellstick Net light.""" def __init__(self, client, device_id): diff --git a/homeassistant/components/tellstick/light.py b/homeassistant/components/tellstick/light.py index cb4fe9b37ec..15b15112d14 100644 --- a/homeassistant/components/tellstick/light.py +++ b/homeassistant/components/tellstick/light.py @@ -1,5 +1,9 @@ """Support for Tellstick lights.""" -from homeassistant.components.light import ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) from . import ( ATTR_DISCOVER_CONFIG, @@ -30,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class TellstickLight(TellstickDevice, Light): +class TellstickLight(TellstickDevice, LightEntity): """Representation of a Tellstick light.""" def __init__(self, tellcore_device, signal_repetitions): diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 1c19dfb33a6..df0a095cdd1 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -13,7 +13,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import ( CONF_ENTITY_ID, @@ -146,7 +146,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(lights) -class LightTemplate(Light): +class LightTemplate(LightEntity): """Representation of a templated Light, including dimmable.""" def __init__( diff --git a/homeassistant/components/tikteck/light.py b/homeassistant/components/tikteck/light.py index 6c623f29f18..22e18d9697b 100644 --- a/homeassistant/components/tikteck/light.py +++ b/homeassistant/components/tikteck/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_NAME, CONF_PASSWORD import homeassistant.helpers.config_validation as cv @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(lights) -class TikteckLight(Light): +class TikteckLight(LightEntity): """Representation of a Tikteck light.""" def __init__(self, device): diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index 9910fe42e03..9c3d73b43b5 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -13,7 +13,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.device_registry as dr @@ -120,7 +120,7 @@ class LightFeatures(NamedTuple): has_emeter: bool -class TPLinkSmartBulb(Light): +class TPLinkSmartBulb(LightEntity): """Representation of a TPLink Smart Bulb.""" def __init__(self, smartbulb: SmartBulb) -> None: diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 40fe7b01cb0..4e44b452d33 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(TradfriGroup(group, api, gateway_id) for group in groups) -class TradfriGroup(TradfriBaseClass, Light): +class TradfriGroup(TradfriBaseClass, LightEntity): """The platform class for light groups required by hass.""" def __init__(self, device, api, gateway_id): @@ -106,7 +106,7 @@ class TradfriGroup(TradfriBaseClass, Light): await self._api(self._device.set_state(1)) -class TradfriLight(TradfriBaseDevice, Light): +class TradfriLight(TradfriBaseDevice, LightEntity): """The platform class required by Home Assistant.""" def __init__(self, device, api, gateway_id): diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 6c05f4f2cc7..2959b9239bd 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -7,7 +7,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.util import color as colorutil @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class TuyaLight(TuyaDevice, Light): +class TuyaLight(TuyaDevice, LightEntity): """Tuya light device.""" def __init__(self, tuya): diff --git a/homeassistant/components/unifiled/light.py b/homeassistant/components/unifiled/light.py index 6b0b1e2edf1..0281aa351d2 100644 --- a/homeassistant/components/unifiled/light.py +++ b/homeassistant/components/unifiled/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME import homeassistant.helpers.config_validation as cv @@ -46,7 +46,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(UnifiLedLight(light, api) for light in api.getlights()) -class UnifiLedLight(Light): +class UnifiLedLight(LightEntity): """Representation of an unifiled Light.""" def __init__(self, light, api): diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index d7654feab2d..4aebbb27953 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -12,7 +12,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_FLASH, SUPPORT_TRANSITION, - Light, + LightEntity, ) from . import VelbusEntity @@ -32,7 +32,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusLight(VelbusEntity, Light): +class VelbusLight(VelbusEntity, LightEntity): """Representation of a Velbus light.""" @property diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 877fdf51f0a..250842f1687 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -9,7 +9,7 @@ from homeassistant.components.light import ( ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -37,7 +37,7 @@ async def async_setup_entry( ) -class VeraLight(VeraDevice, Light): +class VeraLight(VeraDevice, LightEntity): """Representation of a Vera Light, including dimmable.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index b8c05ead076..02b5e79d1c6 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -15,7 +15,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect import homeassistant.util.color as color_util @@ -76,7 +76,7 @@ def setup_bridge(hass, bridge, async_add_entities): update_lights() -class WemoLight(Light): +class WemoLight(LightEntity): """Representation of a WeMo light.""" def __init__(self, device, update_lights): @@ -209,7 +209,7 @@ class WemoLight(Light): await self.hass.async_add_executor_job(self._update, force_update) -class WemoDimmer(Light): +class WemoDimmer(LightEntity): """Representation of a WeMo dimmer.""" def __init__(self, device): diff --git a/homeassistant/components/wink/light.py b/homeassistant/components/wink/light.py index bd125e6a7c2..4d20cf4dd5a 100644 --- a/homeassistant/components/wink/light.py +++ b/homeassistant/components/wink/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.util import color as color_util from homeassistant.util.color import ( @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([WinkLight(light, hass)]) -class WinkLight(WinkDevice, Light): +class WinkLight(WinkDevice, LightEntity): """Representation of a Wink light.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index beda19b8101..6b86be6265b 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -17,7 +17,7 @@ from homeassistant.components.light import ( SUPPORT_EFFECT, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers import config_validation as cv, entity_platform @@ -78,7 +78,7 @@ async def async_setup_entry( async_add_entities(lights, True) -class WLEDLight(Light, WLEDDeviceEntity): +class WLEDLight(LightEntity, WLEDDeviceEntity): """Defines a WLED light.""" def __init__( diff --git a/homeassistant/components/x10/light.py b/homeassistant/components/x10/light.py index 131cc61ed61..3943c081eef 100644 --- a/homeassistant/components/x10/light.py +++ b/homeassistant/components/x10/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, - Light, + LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_ID, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -50,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(X10Light(light, is_cm11a) for light in config[CONF_DEVICES]) -class X10Light(Light): +class X10Light(LightEntity): """Representation of an X10 Light.""" def __init__(self, light, is_cm11a): diff --git a/homeassistant/components/xiaomi_aqara/light.py b/homeassistant/components/xiaomi_aqara/light.py index 5096bc25d24..f1cd17f9dee 100644 --- a/homeassistant/components/xiaomi_aqara/light.py +++ b/homeassistant/components/xiaomi_aqara/light.py @@ -8,7 +8,7 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) import homeassistant.util.color as color_util @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class XiaomiGatewayLight(XiaomiDevice, Light): +class XiaomiGatewayLight(XiaomiDevice, LightEntity): """Representation of a XiaomiGatewayLight.""" def __init__(self, device, name, xiaomi_hub): diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index c4ea831ceeb..cc9343aa2c0 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -24,7 +24,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, - Light, + LightEntity, ) from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN from homeassistant.exceptions import PlatformNotReady @@ -236,7 +236,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class XiaomiPhilipsAbstractLight(Light): +class XiaomiPhilipsAbstractLight(LightEntity): """Representation of a Abstract Xiaomi Philips Light.""" def __init__(self, name, light, model, unique_id): diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index ab49e46938c..49315117e72 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -29,7 +29,7 @@ from homeassistant.components.light import ( SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE, CONF_HOST, CONF_NAME from homeassistant.core import callback @@ -426,7 +426,7 @@ def setup_services(hass): ) -class YeelightGenericLight(Light): +class YeelightGenericLight(LightEntity): """Representation of a Yeelight generic light.""" def __init__(self, device, custom_effects=None): diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index c49c874dc21..2e17b92c90a 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -10,7 +10,7 @@ from homeassistant.components.light import ( PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, - Light, + LightEntity, ) from homeassistant.const import CONF_HOST import homeassistant.helpers.config_validation as cv @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(SunflowerBulb(light) for light in hub.get_lights()) -class SunflowerBulb(Light): +class SunflowerBulb(LightEntity): """Representation of a Yeelight Sunflower Light.""" def __init__(self, light): diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py index 42746c6bad3..fa2c7d50add 100644 --- a/homeassistant/components/zengge/light.py +++ b/homeassistant/components/zengge/light.py @@ -12,7 +12,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(lights, True) -class ZenggeLight(Light): +class ZenggeLight(LightEntity): """Representation of a Zengge light.""" def __init__(self, device): diff --git a/homeassistant/components/zha/light.py b/homeassistant/components/zha/light.py index b05e4b7bee0..efe95ae6604 100644 --- a/homeassistant/components/zha/light.py +++ b/homeassistant/components/zha/light.py @@ -97,7 +97,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) -class BaseLight(LogMixin, light.Light): +class BaseLight(LogMixin, light.LightEntity): """Operations common to all light entities.""" def __init__(self, *args, **kwargs): diff --git a/homeassistant/components/zigbee/light.py b/homeassistant/components/zigbee/light.py index 54f6044c3dd..10bb87aa426 100644 --- a/homeassistant/components/zigbee/light.py +++ b/homeassistant/components/zigbee/light.py @@ -1,7 +1,7 @@ """Support for Zigbee lights.""" import voluptuous as vol -from homeassistant.components.light import Light +from homeassistant.components.light import LightEntity from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig @@ -21,5 +21,5 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ZigBeeLight(ZigBeeDigitalOutConfig(config), zigbee_device)]) -class ZigBeeLight(ZigBeeDigitalOut, Light): +class ZigBeeLight(ZigBeeDigitalOut, LightEntity): """Use ZigBeeDigitalOut as light.""" diff --git a/homeassistant/components/zwave/light.py b/homeassistant/components/zwave/light.py index 745400e5c44..1856aeb9623 100644 --- a/homeassistant/components/zwave/light.py +++ b/homeassistant/components/zwave/light.py @@ -14,7 +14,7 @@ from homeassistant.components.light import ( SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, - Light, + LightEntity, ) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.core import callback @@ -118,7 +118,7 @@ def ct_to_hs(temp): return [int(val) for val in colorlist] -class ZwaveDimmer(ZWaveDeviceEntity, Light): +class ZwaveDimmer(ZWaveDeviceEntity, LightEntity): """Representation of a Z-Wave dimmer.""" def __init__(self, values, refresh, delay): diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 2e2f74828d9..e53949ef518 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -548,3 +548,13 @@ async def test_light_brightness_pct_conversion(hass): _, data = entity.last_call("turn_on") assert data["brightness"] == 255, data + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomLight(light.Light): + pass + + CustomLight() + assert "Light is deprecated, modify CustomLight" in caplog.text diff --git a/tests/testing_config/custom_components/test/light.py b/tests/testing_config/custom_components/test/light.py index d3f96c367d8..863412fe747 100644 --- a/tests/testing_config/custom_components/test/light.py +++ b/tests/testing_config/custom_components/test/light.py @@ -3,7 +3,7 @@ Provide a mock light platform. Call init before using it in your tests to ensure clean test data. """ -from homeassistant.components.light import Light +from homeassistant.components.light import LightEntity from homeassistant.const import STATE_OFF, STATE_ON from tests.common import MockToggleEntity @@ -33,7 +33,7 @@ async def async_setup_platform( async_add_entities_callback(ENTITIES) -class MockLight(MockToggleEntity, Light): +class MockLight(MockToggleEntity, LightEntity): """Mock light class.""" brightness = None From 066e921a8b6f5264de4a93f604aed789c9730b73 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 26 Apr 2020 18:50:37 +0200 Subject: [PATCH 076/511] Rename SwitchDevice to SwitchEntity (#34673) --- homeassistant/components/abode/switch.py | 6 +++--- homeassistant/components/acer_projector/switch.py | 4 ++-- homeassistant/components/adguard/switch.py | 4 ++-- homeassistant/components/ads/switch.py | 4 ++-- .../components/android_ip_webcam/switch.py | 4 ++-- homeassistant/components/anel_pwrctrl/switch.py | 4 ++-- homeassistant/components/aqualogic/switch.py | 4 ++-- homeassistant/components/arduino/switch.py | 4 ++-- homeassistant/components/arest/switch.py | 4 ++-- homeassistant/components/aten_pe/switch.py | 4 ++-- homeassistant/components/axis/switch.py | 4 ++-- homeassistant/components/broadlink/switch.py | 4 ++-- homeassistant/components/command_line/switch.py | 4 ++-- homeassistant/components/danfoss_air/switch.py | 4 ++-- homeassistant/components/deconz/switch.py | 6 +++--- homeassistant/components/demo/switch.py | 4 ++-- homeassistant/components/digital_ocean/switch.py | 4 ++-- homeassistant/components/digitalloggers/switch.py | 4 ++-- homeassistant/components/dlink/switch.py | 4 ++-- homeassistant/components/doorbird/switch.py | 4 ++-- homeassistant/components/dynalite/switch.py | 4 ++-- homeassistant/components/ecoal_boiler/switch.py | 4 ++-- homeassistant/components/edimax/switch.py | 4 ++-- homeassistant/components/elkm1/switch.py | 4 ++-- homeassistant/components/elv/switch.py | 4 ++-- homeassistant/components/esphome/switch.py | 4 ++-- homeassistant/components/eufy/switch.py | 4 ++-- homeassistant/components/fibaro/switch.py | 4 ++-- homeassistant/components/flux/switch.py | 4 ++-- homeassistant/components/freebox/switch.py | 4 ++-- homeassistant/components/fritzbox/switch.py | 4 ++-- homeassistant/components/geniushub/switch.py | 4 ++-- homeassistant/components/hdmi_cec/switch.py | 4 ++-- homeassistant/components/hikvisioncam/switch.py | 4 ++-- homeassistant/components/hive/switch.py | 4 ++-- .../components/homekit_controller/switch.py | 6 +++--- homeassistant/components/homematic/switch.py | 4 ++-- .../components/homematicip_cloud/switch.py | 8 ++++---- homeassistant/components/huawei_lte/switch.py | 4 ++-- homeassistant/components/hydrawise/switch.py | 4 ++-- homeassistant/components/iaqualink/switch.py | 4 ++-- homeassistant/components/ihc/switch.py | 4 ++-- homeassistant/components/insteon/switch.py | 6 +++--- homeassistant/components/isy994/switch.py | 4 ++-- homeassistant/components/juicenet/switch.py | 4 ++-- homeassistant/components/kankun/switch.py | 4 ++-- homeassistant/components/knx/switch.py | 4 ++-- homeassistant/components/lcn/switch.py | 6 +++--- homeassistant/components/lightwave/switch.py | 4 ++-- homeassistant/components/linode/switch.py | 4 ++-- homeassistant/components/litejet/switch.py | 4 ++-- homeassistant/components/lupusec/switch.py | 4 ++-- homeassistant/components/lutron/switch.py | 6 +++--- homeassistant/components/lutron_caseta/switch.py | 4 ++-- homeassistant/components/mfi/switch.py | 4 ++-- homeassistant/components/mochad/switch.py | 4 ++-- homeassistant/components/mqtt/switch.py | 4 ++-- homeassistant/components/mysensors/switch.py | 4 ++-- homeassistant/components/mystrom/switch.py | 4 ++-- homeassistant/components/n26/switch.py | 4 ++-- homeassistant/components/netio/switch.py | 4 ++-- homeassistant/components/orvibo/switch.py | 4 ++-- homeassistant/components/pcal9535a/switch.py | 4 ++-- homeassistant/components/pencom/switch.py | 4 ++-- homeassistant/components/pi4ioe5v9xxxx/switch.py | 4 ++-- homeassistant/components/pilight/switch.py | 4 ++-- .../components/pulseaudio_loopback/switch.py | 4 ++-- homeassistant/components/qwikswitch/__init__.py | 2 +- homeassistant/components/qwikswitch/switch.py | 4 ++-- homeassistant/components/rachio/switch.py | 4 ++-- homeassistant/components/rainbird/switch.py | 4 ++-- homeassistant/components/raincloud/switch.py | 4 ++-- homeassistant/components/rainmachine/switch.py | 4 ++-- homeassistant/components/raspyrfm/switch.py | 4 ++-- homeassistant/components/recswitch/switch.py | 4 ++-- homeassistant/components/rest/switch.py | 4 ++-- homeassistant/components/rflink/switch.py | 4 ++-- homeassistant/components/rfxtrx/switch.py | 4 ++-- homeassistant/components/ring/switch.py | 4 ++-- homeassistant/components/rpi_rf/switch.py | 4 ++-- homeassistant/components/satel_integra/switch.py | 4 ++-- homeassistant/components/scsgate/switch.py | 4 ++-- homeassistant/components/skybell/switch.py | 4 ++-- homeassistant/components/smappee/switch.py | 4 ++-- homeassistant/components/smartthings/switch.py | 4 ++-- homeassistant/components/snmp/switch.py | 4 ++-- homeassistant/components/somfy/switch.py | 4 ++-- homeassistant/components/sony_projector/switch.py | 4 ++-- homeassistant/components/spider/switch.py | 4 ++-- homeassistant/components/starline/switch.py | 4 ++-- homeassistant/components/supla/switch.py | 4 ++-- homeassistant/components/switch/__init__.py | 14 +++++++++++++- homeassistant/components/switchbot/switch.py | 4 ++-- homeassistant/components/switcher_kis/switch.py | 4 ++-- homeassistant/components/switchmate/switch.py | 4 ++-- homeassistant/components/tahoma/switch.py | 4 ++-- homeassistant/components/telnet/switch.py | 4 ++-- homeassistant/components/template/switch.py | 4 ++-- homeassistant/components/tesla/switch.py | 10 +++++----- homeassistant/components/tplink/switch.py | 4 ++-- homeassistant/components/tradfri/switch.py | 4 ++-- homeassistant/components/tuya/switch.py | 4 ++-- homeassistant/components/unifi/switch.py | 6 +++--- homeassistant/components/upcloud/switch.py | 4 ++-- homeassistant/components/velbus/switch.py | 4 ++-- homeassistant/components/vera/switch.py | 4 ++-- homeassistant/components/verisure/switch.py | 4 ++-- homeassistant/components/versasense/switch.py | 4 ++-- homeassistant/components/vesync/switch.py | 6 +++--- homeassistant/components/vultr/switch.py | 4 ++-- homeassistant/components/wake_on_lan/switch.py | 4 ++-- homeassistant/components/wemo/switch.py | 4 ++-- homeassistant/components/wirelesstag/switch.py | 4 ++-- homeassistant/components/wled/switch.py | 4 ++-- homeassistant/components/xiaomi_aqara/switch.py | 4 ++-- homeassistant/components/xiaomi_miio/switch.py | 4 ++-- homeassistant/components/zha/switch.py | 4 ++-- homeassistant/components/zigbee/switch.py | 4 ++-- homeassistant/components/zoneminder/switch.py | 4 ++-- homeassistant/components/zwave/switch.py | 4 ++-- tests/components/switch/test_init.py | 10 ++++++++++ 121 files changed, 273 insertions(+), 251 deletions(-) diff --git a/homeassistant/components/abode/switch.py b/homeassistant/components/abode/switch.py index bbd90442cd9..0985ce5ce2a 100644 --- a/homeassistant/components/abode/switch.py +++ b/homeassistant/components/abode/switch.py @@ -1,7 +1,7 @@ """Support for Abode Security System switches.""" import abodepy.helpers.constants as CONST -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from . import AbodeAutomation, AbodeDevice @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class AbodeSwitch(AbodeDevice, SwitchDevice): +class AbodeSwitch(AbodeDevice, SwitchEntity): """Representation of an Abode switch.""" def turn_on(self, **kwargs): @@ -45,7 +45,7 @@ class AbodeSwitch(AbodeDevice, SwitchDevice): return self._device.is_on -class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice): +class AbodeAutomationSwitch(AbodeAutomation, SwitchEntity): """A switch implementation for Abode automations.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/acer_projector/switch.py b/homeassistant/components/acer_projector/switch.py index 9afc9963522..f947f3fe0c0 100644 --- a/homeassistant/components/acer_projector/switch.py +++ b/homeassistant/components/acer_projector/switch.py @@ -5,7 +5,7 @@ import re import serial import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_FILENAME, CONF_NAME, @@ -69,7 +69,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([AcerSwitch(serial_port, name, timeout, write_timeout)], True) -class AcerSwitch(SwitchDevice): +class AcerSwitch(SwitchEntity): """Represents an Acer Projector as a switch.""" def __init__(self, serial_port, name, timeout, write_timeout, **kwargs): diff --git a/homeassistant/components/adguard/switch.py b/homeassistant/components/adguard/switch.py index 1ddefb3367b..78d2769ce5d 100644 --- a/homeassistant/components/adguard/switch.py +++ b/homeassistant/components/adguard/switch.py @@ -10,7 +10,7 @@ from homeassistant.components.adguard.const import ( DATA_ADGUARD_VERION, DOMAIN, ) -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType @@ -45,7 +45,7 @@ async def async_setup_entry( async_add_entities(switches, True) -class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchDevice): +class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity): """Defines a AdGuard Home switch.""" def __init__( diff --git a/homeassistant/components/ads/switch.py b/homeassistant/components/ads/switch.py index 64c797ff309..c8231d8a31d 100644 --- a/homeassistant/components/ads/switch.py +++ b/homeassistant/components/ads/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([AdsSwitch(ads_hub, name, ads_var)]) -class AdsSwitch(AdsEntity, SwitchDevice): +class AdsSwitch(AdsEntity, SwitchEntity): """Representation of an ADS switch device.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/android_ip_webcam/switch.py b/homeassistant/components/android_ip_webcam/switch.py index a494e78bdc7..bdbb37e7661 100644 --- a/homeassistant/components/android_ip_webcam/switch.py +++ b/homeassistant/components/android_ip_webcam/switch.py @@ -1,5 +1,5 @@ """Support for Android IP Webcam settings.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import ( CONF_HOST, @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(all_switches, True) -class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice): +class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchEntity): """An abstract class for an IP Webcam setting.""" def __init__(self, name, host, ipcam, setting): diff --git a/homeassistant/components/anel_pwrctrl/switch.py b/homeassistant/components/anel_pwrctrl/switch.py index cc6011d9b65..c769f51d5b6 100644 --- a/homeassistant/components/anel_pwrctrl/switch.py +++ b/homeassistant/components/anel_pwrctrl/switch.py @@ -5,7 +5,7 @@ import logging from anel_pwrctrl import DeviceMaster import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class PwrCtrlSwitch(SwitchDevice): +class PwrCtrlSwitch(SwitchEntity): """Representation of a PwrCtrl switch.""" def __init__(self, port, parent_device): diff --git a/homeassistant/components/aqualogic/switch.py b/homeassistant/components/aqualogic/switch.py index d949175bc6e..94822eaef18 100644 --- a/homeassistant/components/aqualogic/switch.py +++ b/homeassistant/components/aqualogic/switch.py @@ -4,7 +4,7 @@ import logging from aqualogic.core import States import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -45,7 +45,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(switches) -class AquaLogicSwitch(SwitchDevice): +class AquaLogicSwitch(SwitchEntity): """Switch implementation for the AquaLogic component.""" def __init__(self, processor, switch_type): diff --git a/homeassistant/components/arduino/switch.py b/homeassistant/components/arduino/switch.py index ea6f36ac7f5..99b73c86fdb 100644 --- a/homeassistant/components/arduino/switch.py +++ b/homeassistant/components/arduino/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class ArduinoSwitch(SwitchDevice): +class ArduinoSwitch(SwitchEntity): """Representation of an Arduino switch.""" def __init__(self, pin, options, board): diff --git a/homeassistant/components/arest/switch.py b/homeassistant/components/arest/switch.py index 875211f5f0b..ddd6b51f76d 100644 --- a/homeassistant/components/arest/switch.py +++ b/homeassistant/components/arest/switch.py @@ -5,7 +5,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME, CONF_RESOURCE, HTTP_OK import homeassistant.helpers.config_validation as cv @@ -80,7 +80,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class ArestSwitchBase(SwitchDevice): +class ArestSwitchBase(SwitchEntity): """Representation of an aREST switch.""" def __init__(self, resource, location, name): diff --git a/homeassistant/components/aten_pe/switch.py b/homeassistant/components/aten_pe/switch.py index 2ec6ec4b83d..e5970fc4d3b 100644 --- a/homeassistant/components/aten_pe/switch.py +++ b/homeassistant/components/aten_pe/switch.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.switch import ( DEVICE_CLASS_OUTLET, PLATFORM_SCHEMA, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import CONF_HOST, CONF_PORT, CONF_USERNAME from homeassistant.exceptions import PlatformNotReady @@ -64,7 +64,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(switches) -class AtenSwitch(SwitchDevice): +class AtenSwitch(SwitchEntity): """Represents an ATEN PE switch.""" def __init__(self, device, mac, outlet, name): diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index ed822543a00..be048a510ed 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -2,7 +2,7 @@ from axis.event_stream import CLASS_OUTPUT -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class AxisSwitch(AxisEventBase, SwitchDevice): +class AxisSwitch(AxisEventBase, SwitchEntity): """Representation of a Axis switch.""" @property diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index 6eed0646d76..4173fa4adc6 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -7,7 +7,7 @@ import socket import broadlink as blk import voluptuous as vol -from homeassistant.components.switch import DOMAIN, PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import DOMAIN, PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_COMMAND_OFF, CONF_COMMAND_ON, @@ -146,7 +146,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class BroadlinkRMSwitch(SwitchDevice, RestoreEntity): +class BroadlinkRMSwitch(SwitchEntity, RestoreEntity): """Representation of an Broadlink switch.""" def __init__( diff --git a/homeassistant/components/command_line/switch.py b/homeassistant/components/command_line/switch.py index f89ac6f5b92..7f62970b639 100644 --- a/homeassistant/components/command_line/switch.py +++ b/homeassistant/components/command_line/switch.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.switch import ( ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import ( CONF_COMMAND_OFF, @@ -66,7 +66,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class CommandSwitch(SwitchDevice): +class CommandSwitch(SwitchEntity): """Representation a switch that can be toggled using shell commands.""" def __init__( diff --git a/homeassistant/components/danfoss_air/switch.py b/homeassistant/components/danfoss_air/switch.py index 96e363951c8..dc4c79ed10f 100644 --- a/homeassistant/components/danfoss_air/switch.py +++ b/homeassistant/components/danfoss_air/switch.py @@ -3,7 +3,7 @@ import logging from pydanfossair.commands import ReadCommand, UpdateCommand -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class DanfossAir(SwitchDevice): +class DanfossAir(SwitchEntity): """Representation of a Danfoss Air HRV Switch.""" def __init__(self, data, name, state_command, on_command, off_command): diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index 1b51256580a..d7b6b55fbb8 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -1,5 +1,5 @@ """Support for deCONZ switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -43,7 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_switch(gateway.api.lights.values()) -class DeconzPowerPlug(DeconzDevice, SwitchDevice): +class DeconzPowerPlug(DeconzDevice, SwitchEntity): """Representation of a deCONZ power plug.""" @property @@ -62,7 +62,7 @@ class DeconzPowerPlug(DeconzDevice, SwitchDevice): await self._device.async_set_state(data) -class DeconzSiren(DeconzDevice, SwitchDevice): +class DeconzSiren(DeconzDevice, SwitchEntity): """Representation of a deCONZ siren.""" @property diff --git a/homeassistant/components/demo/switch.py b/homeassistant/components/demo/switch.py index 5050b2283b4..cdbeb142677 100644 --- a/homeassistant/components/demo/switch.py +++ b/homeassistant/components/demo/switch.py @@ -1,5 +1,5 @@ """Demo platform that has two fake switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import DEVICE_DEFAULT_NAME from . import DOMAIN @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoSwitch(SwitchDevice): +class DemoSwitch(SwitchEntity): """Representation of a demo switch.""" def __init__(self, unique_id, name, state, icon, assumed, device_class=None): diff --git a/homeassistant/components/digital_ocean/switch.py b/homeassistant/components/digital_ocean/switch.py index 9b9b8157bce..811b844e35c 100644 --- a/homeassistant/components/digital_ocean/switch.py +++ b/homeassistant/components/digital_ocean/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv @@ -50,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class DigitalOceanSwitch(SwitchDevice): +class DigitalOceanSwitch(SwitchEntity): """Representation of a Digital Ocean droplet switch.""" def __init__(self, do, droplet_id): diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py index 268ec581c00..7448b9fbcf3 100644 --- a/homeassistant/components/digitalloggers/switch.py +++ b/homeassistant/components/digitalloggers/switch.py @@ -5,7 +5,7 @@ import logging import dlipower import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -72,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(outlets) -class DINRelay(SwitchDevice): +class DINRelay(SwitchEntity): """Representation of an individual DIN III relay port.""" def __init__(self, controller_name, parent_device, outlet): diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index 1cc0e16f30f..c173c879ad1 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -6,7 +6,7 @@ import urllib from pyW215.pyW215 import SmartPlug import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( ATTR_TEMPERATURE, CONF_HOST, @@ -56,7 +56,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SmartPlugSwitch(hass, data, name)], True) -class SmartPlugSwitch(SwitchDevice): +class SmartPlugSwitch(SwitchEntity): """Representation of a D-Link Smart Plug switch.""" def __init__(self, hass, data, name): diff --git a/homeassistant/components/doorbird/switch.py b/homeassistant/components/doorbird/switch.py index 9f292803b8b..1e4cb81a5eb 100644 --- a/homeassistant/components/doorbird/switch.py +++ b/homeassistant/components/doorbird/switch.py @@ -2,7 +2,7 @@ import datetime import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity import homeassistant.util.dt as dt_util from .const import DOMAIN, DOOR_STATION, DOOR_STATION_INFO @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class DoorBirdSwitch(DoorBirdEntity, SwitchDevice): +class DoorBirdSwitch(DoorBirdEntity, SwitchEntity): """A relay in a DoorBird device.""" def __init__(self, doorstation, doorstation_info, relay): diff --git a/homeassistant/components/dynalite/switch.py b/homeassistant/components/dynalite/switch.py index 45e24d8193a..d106d976d68 100644 --- a/homeassistant/components/dynalite/switch.py +++ b/homeassistant/components/dynalite/switch.py @@ -1,7 +1,7 @@ """Support for the Dynalite channels and presets as switches.""" from typing import Callable -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -18,7 +18,7 @@ async def async_setup_entry( ) -class DynaliteSwitch(DynaliteBase, SwitchDevice): +class DynaliteSwitch(DynaliteBase, SwitchEntity): """Representation of a Dynalite Channel as a Home Assistant Switch.""" @property diff --git a/homeassistant/components/ecoal_boiler/switch.py b/homeassistant/components/ecoal_boiler/switch.py index 00bfd7f3e5b..bd3b216d705 100644 --- a/homeassistant/components/ecoal_boiler/switch.py +++ b/homeassistant/components/ecoal_boiler/switch.py @@ -2,7 +2,7 @@ import logging from typing import Optional -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import AVAILABLE_PUMPS, DATA_ECOAL_BOILER @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches, True) -class EcoalSwitch(SwitchDevice): +class EcoalSwitch(SwitchEntity): """Representation of Ecoal switch.""" def __init__(self, ecoal_contr, name, state_attr): diff --git a/homeassistant/components/edimax/switch.py b/homeassistant/components/edimax/switch.py index e44ec23bca7..17a43f36235 100644 --- a/homeassistant/components/edimax/switch.py +++ b/homeassistant/components/edimax/switch.py @@ -4,7 +4,7 @@ import logging from pyedimax.smartplug import SmartPlug import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME import homeassistant.helpers.config_validation as cv @@ -35,7 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SmartPlugSwitch(SmartPlug(host, auth), name)], True) -class SmartPlugSwitch(SwitchDevice): +class SmartPlugSwitch(SwitchEntity): """Representation an Edimax Smart Plug switch.""" def __init__(self, smartplug, name): diff --git a/homeassistant/components/elkm1/switch.py b/homeassistant/components/elkm1/switch.py index af32e81bc4c..d9eb59737b6 100644 --- a/homeassistant/components/elkm1/switch.py +++ b/homeassistant/components/elkm1/switch.py @@ -1,5 +1,5 @@ """Support for control of ElkM1 outputs (relays).""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import ElkAttachedEntity, create_elk_entities from .const import DOMAIN @@ -14,7 +14,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class ElkOutput(ElkAttachedEntity, SwitchDevice): +class ElkOutput(ElkAttachedEntity, SwitchEntity): """Elk output as switch.""" @property diff --git a/homeassistant/components/elv/switch.py b/homeassistant/components/elv/switch.py index d867d286f50..12b21c23d1a 100644 --- a/homeassistant/components/elv/switch.py +++ b/homeassistant/components/elv/switch.py @@ -4,7 +4,7 @@ import logging import pypca from serial import SerialException -from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchDevice +from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchEntity from homeassistant.const import EVENT_HOMEASSISTANT_STOP _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): pca.start_scan() -class SmartPlugSwitch(SwitchDevice): +class SmartPlugSwitch(SwitchEntity): """Representation of a PCA Smart Plug switch.""" def __init__(self, pca, device_id): diff --git a/homeassistant/components/esphome/switch.py b/homeassistant/components/esphome/switch.py index b52d630e1b4..a3c7eeab946 100644 --- a/homeassistant/components/esphome/switch.py +++ b/homeassistant/components/esphome/switch.py @@ -4,7 +4,7 @@ from typing import Optional from aioesphomeapi import SwitchInfo, SwitchState -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -28,7 +28,7 @@ async def async_setup_entry( ) -class EsphomeSwitch(EsphomeEntity, SwitchDevice): +class EsphomeSwitch(EsphomeEntity, SwitchEntity): """A switch implementation for ESPHome.""" @property diff --git a/homeassistant/components/eufy/switch.py b/homeassistant/components/eufy/switch.py index cbc09f4101c..586965aa42b 100644 --- a/homeassistant/components/eufy/switch.py +++ b/homeassistant/components/eufy/switch.py @@ -3,7 +3,7 @@ import logging import lakeside -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity _LOGGER = logging.getLogger(__name__) @@ -15,7 +15,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EufySwitch(discovery_info)], True) -class EufySwitch(SwitchDevice): +class EufySwitch(SwitchEntity): """Representation of a Eufy switch.""" def __init__(self, device): diff --git a/homeassistant/components/fibaro/switch.py b/homeassistant/components/fibaro/switch.py index b00e5817c9e..a38f642775f 100644 --- a/homeassistant/components/fibaro/switch.py +++ b/homeassistant/components/fibaro/switch.py @@ -1,7 +1,7 @@ """Support for Fibaro switches.""" import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.util import convert from . import FIBARO_DEVICES, FibaroDevice @@ -19,7 +19,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class FibaroSwitch(FibaroDevice, SwitchDevice): +class FibaroSwitch(FibaroDevice, SwitchEntity): """Representation of a Fibaro Switch.""" def __init__(self, fibaro_device): diff --git a/homeassistant/components/flux/switch.py b/homeassistant/components/flux/switch.py index 61bdb9a2862..8a27c99c78d 100644 --- a/homeassistant/components/flux/switch.py +++ b/homeassistant/components/flux/switch.py @@ -19,7 +19,7 @@ from homeassistant.components.light import ( VALID_TRANSITION, is_on, ) -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.const import ( ATTR_ENTITY_ID, CONF_LIGHTS, @@ -168,7 +168,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= hass.services.async_register(DOMAIN, service_name, async_update) -class FluxSwitch(SwitchDevice, RestoreEntity): +class FluxSwitch(SwitchEntity, RestoreEntity): """Representation of a Flux switch.""" def __init__( diff --git a/homeassistant/components/freebox/switch.py b/homeassistant/components/freebox/switch.py index 9e1011d5d3c..7f8934d9d65 100644 --- a/homeassistant/components/freebox/switch.py +++ b/homeassistant/components/freebox/switch.py @@ -4,7 +4,7 @@ from typing import Dict from aiofreepybox.exceptions import InsufficientPermissionsError -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -22,7 +22,7 @@ async def async_setup_entry( async_add_entities([FreeboxWifiSwitch(router)], True) -class FreeboxWifiSwitch(SwitchDevice): +class FreeboxWifiSwitch(SwitchEntity): """Representation of a freebox wifi switch.""" def __init__(self, router: FreeboxRouter) -> None: diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 6f98667304b..b179464182f 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -1,7 +1,7 @@ """Support for AVM Fritz!Box smarthome switch devices.""" import requests -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import ( ATTR_TEMPERATURE, CONF_DEVICES, @@ -37,7 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities) -class FritzboxSwitch(SwitchDevice): +class FritzboxSwitch(SwitchEntity): """The switch class for Fritzbox switches.""" def __init__(self, device, fritz): diff --git a/homeassistant/components/geniushub/switch.py b/homeassistant/components/geniushub/switch.py index b73c9a89041..e73468321bd 100644 --- a/homeassistant/components/geniushub/switch.py +++ b/homeassistant/components/geniushub/switch.py @@ -1,5 +1,5 @@ """Support for Genius Hub switch/outlet devices.""" -from homeassistant.components.switch import DEVICE_CLASS_OUTLET, SwitchDevice +from homeassistant.components.switch import DEVICE_CLASS_OUTLET, SwitchEntity from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import DOMAIN, GeniusZone @@ -27,7 +27,7 @@ async def async_setup_platform( ) -class GeniusSwitch(GeniusZone, SwitchDevice): +class GeniusSwitch(GeniusZone, SwitchEntity): """Representation of a Genius Hub switch.""" @property diff --git a/homeassistant/components/hdmi_cec/switch.py b/homeassistant/components/hdmi_cec/switch.py index 0fcf9c01c8f..aaaa2b83054 100644 --- a/homeassistant/components/hdmi_cec/switch.py +++ b/homeassistant/components/hdmi_cec/switch.py @@ -1,7 +1,7 @@ """Support for HDMI CEC devices as switches.""" import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY from . import ATTR_NEW, CecDevice @@ -22,7 +22,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(entities, True) -class CecSwitchDevice(CecDevice, SwitchDevice): +class CecSwitchDevice(CecDevice, SwitchEntity): """Representation of a HDMI device as a Switch.""" def __init__(self, device, logical) -> None: diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py index f86853a5468..2e924135bd4 100644 --- a/homeassistant/components/hikvisioncam/switch.py +++ b/homeassistant/components/hikvisioncam/switch.py @@ -5,7 +5,7 @@ import hikvision.api from hikvision.error import HikvisionError, MissingParamError import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([HikvisionMotionSwitch(name, hikvision_cam)]) -class HikvisionMotionSwitch(SwitchDevice): +class HikvisionMotionSwitch(SwitchEntity): """Representation of a switch to toggle on/off motion detection.""" def __init__(self, name, hikvision_cam): diff --git a/homeassistant/components/hive/switch.py b/homeassistant/components/hive/switch.py index 53e1ec6a069..734581b0db3 100644 --- a/homeassistant/components/hive/switch.py +++ b/homeassistant/components/hive/switch.py @@ -1,5 +1,5 @@ """Support for the Hive switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DATA_HIVE, DOMAIN, HiveEntity, refresh_system @@ -16,7 +16,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class HiveDevicePlug(HiveEntity, SwitchDevice): +class HiveDevicePlug(HiveEntity, SwitchEntity): """Hive Active Plug.""" @property diff --git a/homeassistant/components/homekit_controller/switch.py b/homeassistant/components/homekit_controller/switch.py index 61595b504ca..32c7fd8515e 100644 --- a/homeassistant/components/homekit_controller/switch.py +++ b/homeassistant/components/homekit_controller/switch.py @@ -7,7 +7,7 @@ from aiohomekit.model.characteristics import ( IsConfiguredValues, ) -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from . import KNOWN_DEVICES, HomeKitEntity @@ -21,7 +21,7 @@ ATTR_IS_CONFIGURED = "is_configured" ATTR_REMAINING_DURATION = "remaining_duration" -class HomeKitSwitch(HomeKitEntity, SwitchDevice): +class HomeKitSwitch(HomeKitEntity, SwitchEntity): """Representation of a Homekit switch.""" def get_characteristic_types(self): @@ -49,7 +49,7 @@ class HomeKitSwitch(HomeKitEntity, SwitchDevice): return {OUTLET_IN_USE: outlet_in_use} -class HomeKitValve(HomeKitEntity, SwitchDevice): +class HomeKitValve(HomeKitEntity, SwitchEntity): """Represents a valve in an irrigation system.""" def get_characteristic_types(self): diff --git a/homeassistant/components/homematic/switch.py b/homeassistant/components/homematic/switch.py index 53679818083..afde1ac8527 100644 --- a/homeassistant/components/homematic/switch.py +++ b/homeassistant/components/homematic/switch.py @@ -1,7 +1,7 @@ """Support for HomeMatic switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from .const import ATTR_DISCOVER_DEVICES from .entity import HMDevice @@ -22,7 +22,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class HMSwitch(HMDevice, SwitchDevice): +class HMSwitch(HMDevice, SwitchEntity): """Representation of a HomeMatic switch.""" @property diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 79f7b9dfa5c..f000aef0695 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -15,7 +15,7 @@ from homematicip.aio.device import ( ) from homematicip.aio.group import AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -67,7 +67,7 @@ async def async_setup_entry( async_add_entities(entities) -class HomematicipSwitch(HomematicipGenericDevice, SwitchDevice): +class HomematicipSwitch(HomematicipGenericDevice, SwitchEntity): """representation of a HomematicIP Cloud switch device.""" def __init__(self, hap: HomematicipHAP, device) -> None: @@ -88,7 +88,7 @@ class HomematicipSwitch(HomematicipGenericDevice, SwitchDevice): await self._device.turn_off() -class HomematicipGroupSwitch(HomematicipGenericDevice, SwitchDevice): +class HomematicipGroupSwitch(HomematicipGenericDevice, SwitchEntity): """representation of a HomematicIP switching group.""" def __init__(self, hap: HomematicipHAP, device, post: str = "Group") -> None: @@ -145,7 +145,7 @@ class HomematicipSwitchMeasuring(HomematicipSwitch): return round(self._device.energyCounter) -class HomematicipMultiSwitch(HomematicipGenericDevice, SwitchDevice): +class HomematicipMultiSwitch(HomematicipGenericDevice, SwitchEntity): """Representation of a HomematicIP Cloud multi switch device.""" def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index 44d2da0c898..45b179f470f 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -8,7 +8,7 @@ import attr from homeassistant.components.switch import ( DEVICE_CLASS_SWITCH, DOMAIN as SWITCH_DOMAIN, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import CONF_URL @@ -30,7 +30,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @attr.s -class HuaweiLteBaseSwitch(HuaweiLteBaseEntity, SwitchDevice): +class HuaweiLteBaseSwitch(HuaweiLteBaseEntity, SwitchEntity): """Huawei LTE switch device base class.""" key: str diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index aa3780060c3..577fde85d37 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -47,7 +47,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class HydrawiseSwitch(HydrawiseEntity, SwitchDevice): +class HydrawiseSwitch(HydrawiseEntity, SwitchEntity): """A switch implementation for Hydrawise device.""" def __init__(self, default_watering_timer, *args): diff --git a/homeassistant/components/iaqualink/switch.py b/homeassistant/components/iaqualink/switch.py index 8efb473cf54..b71253ade12 100644 --- a/homeassistant/components/iaqualink/switch.py +++ b/homeassistant/components/iaqualink/switch.py @@ -1,7 +1,7 @@ """Support for Aqualink pool feature switches.""" import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -23,7 +23,7 @@ async def async_setup_entry( async_add_entities(devs, True) -class HassAqualinkSwitch(AqualinkEntity, SwitchDevice): +class HassAqualinkSwitch(AqualinkEntity, SwitchEntity): """Representation of a switch.""" @property diff --git a/homeassistant/components/ihc/switch.py b/homeassistant/components/ihc/switch.py index 15994f13eb2..8f72ac3bab8 100644 --- a/homeassistant/components/ihc/switch.py +++ b/homeassistant/components/ihc/switch.py @@ -1,5 +1,5 @@ """Support for IHC switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import IHC_CONTROLLER, IHC_INFO from .const import CONF_OFF_ID, CONF_ON_ID @@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class IHCSwitch(IHCDevice, SwitchDevice): +class IHCSwitch(IHCDevice, SwitchEntity): """Representation of an IHC switch.""" def __init__( diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index c88d31d2f91..3a0668459c9 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -1,7 +1,7 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from .insteon_entity import InsteonEntity @@ -32,7 +32,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([new_entity]) -class InsteonSwitchDevice(InsteonEntity, SwitchDevice): +class InsteonSwitchDevice(InsteonEntity, SwitchEntity): """A Class for an Insteon device.""" @property @@ -49,7 +49,7 @@ class InsteonSwitchDevice(InsteonEntity, SwitchDevice): self._insteon_device_state.off() -class InsteonOpenClosedDevice(InsteonEntity, SwitchDevice): +class InsteonOpenClosedDevice(InsteonEntity, SwitchEntity): """A Class for an Insteon device.""" @property diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index aadd4428abd..d5339960282 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -2,7 +2,7 @@ import logging from typing import Callable -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.helpers.typing import ConfigType from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice @@ -25,7 +25,7 @@ def setup_platform( add_entities(devices) -class ISYSwitchDevice(ISYDevice, SwitchDevice): +class ISYSwitchDevice(ISYDevice, SwitchEntity): """Representation of an ISY994 switch device.""" @property diff --git a/homeassistant/components/juicenet/switch.py b/homeassistant/components/juicenet/switch.py index 30bb5b22814..5f2d16fa39d 100644 --- a/homeassistant/components/juicenet/switch.py +++ b/homeassistant/components/juicenet/switch.py @@ -1,7 +1,7 @@ """Support for monitoring juicenet/juicepoint/juicebox based EVSE switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN, JuicenetDevice @@ -19,7 +19,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class JuicenetChargeNowSwitch(JuicenetDevice, SwitchDevice): +class JuicenetChargeNowSwitch(JuicenetDevice, SwitchEntity): """Implementation of a Juicenet switch.""" def __init__(self, device, hass): diff --git a/homeassistant/components/kankun/switch.py b/homeassistant/components/kankun/switch.py index d9f4db62572..ce179515e88 100644 --- a/homeassistant/components/kankun/switch.py +++ b/homeassistant/components/kankun/switch.py @@ -4,7 +4,7 @@ import logging import requests import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): add_entities_callback(devices) -class KankunSwitch(SwitchDevice): +class KankunSwitch(SwitchEntity): """Representation of a Kankun Wifi switch.""" def __init__(self, hass, name, host, port, path, user, passwd): diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index ae798bf4c08..3f06221cea5 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -2,7 +2,7 @@ import voluptuous as vol from xknx.devices import Switch as XknxSwitch -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -52,7 +52,7 @@ def async_add_entities_config(hass, config, async_add_entities): async_add_entities([KNXSwitch(switch)]) -class KNXSwitch(SwitchDevice): +class KNXSwitch(SwitchEntity): """Representation of a KNX switch.""" def __init__(self, device): diff --git a/homeassistant/components/lcn/switch.py b/homeassistant/components/lcn/switch.py index a2adda95b3b..e441ce40383 100644 --- a/homeassistant/components/lcn/switch.py +++ b/homeassistant/components/lcn/switch.py @@ -1,7 +1,7 @@ """Support for LCN switches.""" import pypck -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import CONF_ADDRESS from . import LcnDevice @@ -34,7 +34,7 @@ async def async_setup_platform( async_add_entities(devices) -class LcnOutputSwitch(LcnDevice, SwitchDevice): +class LcnOutputSwitch(LcnDevice, SwitchEntity): """Representation of a LCN switch for output ports.""" def __init__(self, config, address_connection): @@ -79,7 +79,7 @@ class LcnOutputSwitch(LcnDevice, SwitchDevice): self.async_write_ha_state() -class LcnRelaySwitch(LcnDevice, SwitchDevice): +class LcnRelaySwitch(LcnDevice, SwitchEntity): """Representation of a LCN switch for relay ports.""" def __init__(self, config, address_connection): diff --git a/homeassistant/components/lightwave/switch.py b/homeassistant/components/lightwave/switch.py index 16c2aa53462..7fa075a0834 100644 --- a/homeassistant/components/lightwave/switch.py +++ b/homeassistant/components/lightwave/switch.py @@ -1,5 +1,5 @@ """Support for LightwaveRF switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import CONF_NAME from . import LIGHTWAVE_LINK @@ -20,7 +20,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(switches) -class LWRFSwitch(SwitchDevice): +class LWRFSwitch(SwitchEntity): """Representation of a LightWaveRF switch.""" def __init__(self, name, device_id, lwlink): diff --git a/homeassistant/components/linode/switch.py b/homeassistant/components/linode/switch.py index deb561a5e32..c9207ec1be7 100644 --- a/homeassistant/components/linode/switch.py +++ b/homeassistant/components/linode/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity import homeassistant.helpers.config_validation as cv from . import ( @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev, True) -class LinodeSwitch(SwitchDevice): +class LinodeSwitch(SwitchEntity): """Representation of a Linode Node switch.""" def __init__(self, li, node_id): diff --git a/homeassistant/components/litejet/switch.py b/homeassistant/components/litejet/switch.py index 7861378d42e..a734dc46d3e 100644 --- a/homeassistant/components/litejet/switch.py +++ b/homeassistant/components/litejet/switch.py @@ -2,7 +2,7 @@ import logging from homeassistant.components import litejet -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity ATTR_NUMBER = "number" @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class LiteJetSwitch(SwitchDevice): +class LiteJetSwitch(SwitchEntity): """Representation of a single LiteJet switch.""" def __init__(self, hass, lj, i, name): diff --git a/homeassistant/components/lupusec/switch.py b/homeassistant/components/lupusec/switch.py index a6864f39ef7..d18fcd7c1f7 100644 --- a/homeassistant/components/lupusec/switch.py +++ b/homeassistant/components/lupusec/switch.py @@ -4,7 +4,7 @@ import logging import lupupy.constants as CONST -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN as LUPUSEC_DOMAIN, LupusecDevice @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class LupusecSwitch(LupusecDevice, SwitchDevice): +class LupusecSwitch(LupusecDevice, SwitchEntity): """Representation of a Lupusec switch.""" def turn_on(self, **kwargs): diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index 64ca5e6f216..d03cb4a1953 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -1,7 +1,7 @@ """Support for Lutron switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import LUTRON_CONTROLLER, LUTRON_DEVICES, LutronDevice @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs, True) -class LutronSwitch(LutronDevice, SwitchDevice): +class LutronSwitch(LutronDevice, SwitchEntity): """Representation of a Lutron Switch.""" def __init__(self, area_name, lutron_device, controller): @@ -63,7 +63,7 @@ class LutronSwitch(LutronDevice, SwitchDevice): self._prev_state = self._lutron_device.level > 0 -class LutronLed(LutronDevice, SwitchDevice): +class LutronLed(LutronDevice, SwitchEntity): """Representation of a Lutron Keypad LED.""" def __init__(self, area_name, keypad_name, scene_device, led_device, controller): diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index 23cd1db8f79..01e61cc9002 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -1,7 +1,7 @@ """Support for Lutron Caseta switches.""" import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from . import LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice @@ -22,7 +22,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class LutronCasetaLight(LutronCasetaDevice, SwitchDevice): +class LutronCasetaLight(LutronCasetaDevice, SwitchEntity): """Representation of a Lutron Caseta switch.""" async def async_turn_on(self, **kwargs): diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py index 08ed841e7e1..d2ba2371303 100644 --- a/homeassistant/components/mfi/switch.py +++ b/homeassistant/components/mfi/switch.py @@ -5,7 +5,7 @@ from mficlient.client import FailedToLogin, MFiClient import requests import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -61,7 +61,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class MfiSwitch(SwitchDevice): +class MfiSwitch(SwitchEntity): """Representation of an mFi switch-able device.""" def __init__(self, port): diff --git a/homeassistant/components/mochad/switch.py b/homeassistant/components/mochad/switch.py index 4ce8f676659..e7f1bee99f6 100644 --- a/homeassistant/components/mochad/switch.py +++ b/homeassistant/components/mochad/switch.py @@ -5,7 +5,7 @@ from pymochad import device from pymochad.exceptions import MochadException import voluptuous as vol -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import CONF_ADDRESS, CONF_DEVICES, CONF_NAME, CONF_PLATFORM from homeassistant.helpers import config_validation as cv @@ -36,7 +36,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class MochadSwitch(SwitchDevice): +class MochadSwitch(SwitchEntity): """Representation of a X10 switch over Mochad.""" def __init__(self, hass, ctrl, dev): diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index bc1f4a038b4..44d028829d0 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.components import mqtt, switch -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import ( CONF_DEVICE, CONF_ICON, @@ -104,7 +104,7 @@ class MqttSwitch( MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, - SwitchDevice, + SwitchEntity, RestoreEntity, ): """Representation of a switch that can be toggled using MQTT.""" diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index 16bb1ee6deb..0da8bfe7030 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -2,7 +2,7 @@ import voluptuous as vol from homeassistant.components import mysensors -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv @@ -72,7 +72,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchDevice): +class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity): """Representation of the value of a MySensors Switch child node.""" @property diff --git a/homeassistant/components/mystrom/switch.py b/homeassistant/components/mystrom/switch.py index ea91c9ba5d7..ab1207658ab 100644 --- a/homeassistant/components/mystrom/switch.py +++ b/homeassistant/components/mystrom/switch.py @@ -5,7 +5,7 @@ from pymystrom.exceptions import MyStromConnectionError from pymystrom.switch import MyStromSwitch as _MyStromSwitch import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -37,7 +37,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([MyStromSwitch(plug, name)]) -class MyStromSwitch(SwitchDevice): +class MyStromSwitch(SwitchEntity): """Representation of a myStrom switch/plug.""" def __init__(self, plug, name): diff --git a/homeassistant/components/n26/switch.py b/homeassistant/components/n26/switch.py index 6ec111720f3..155c9c0091b 100644 --- a/homeassistant/components/n26/switch.py +++ b/homeassistant/components/n26/switch.py @@ -1,7 +1,7 @@ """Support for N26 switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DEFAULT_SCAN_INTERVAL, DOMAIN from .const import CARD_STATE_ACTIVE, CARD_STATE_BLOCKED, DATA @@ -26,7 +26,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switch_entities) -class N26CardSwitch(SwitchDevice): +class N26CardSwitch(SwitchEntity): """Representation of a N26 card block/unblock switch.""" def __init__(self, api_data, card: dict): diff --git a/homeassistant/components/netio/switch.py b/homeassistant/components/netio/switch.py index 6baa3a63f9f..08a5b7df862 100644 --- a/homeassistant/components/netio/switch.py +++ b/homeassistant/components/netio/switch.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant import util from homeassistant.components.http import HomeAssistantView -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, @@ -125,7 +125,7 @@ class NetioApiView(HomeAssistantView): return self.json(True) -class NetioSwitch(SwitchDevice): +class NetioSwitch(SwitchEntity): """Provide a Netio linked switch.""" def __init__(self, netio, outlet, name): diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py index 75a95e053ae..fec30cdade7 100644 --- a/homeassistant/components/orvibo/switch.py +++ b/homeassistant/components/orvibo/switch.py @@ -4,7 +4,7 @@ import logging from orvibo.s20 import S20, S20Exception, discover import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_DISCOVERY, CONF_HOST, @@ -62,7 +62,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): add_entities_callback(switches) -class S20Switch(SwitchDevice): +class S20Switch(SwitchEntity): """Representation of an S20 switch.""" def __init__(self, name, s20): diff --git a/homeassistant/components/pcal9535a/switch.py b/homeassistant/components/pcal9535a/switch.py index 87c8ced1b0d..7b4cc919dbd 100644 --- a/homeassistant/components/pcal9535a/switch.py +++ b/homeassistant/components/pcal9535a/switch.py @@ -4,7 +4,7 @@ import logging from pcal9535a import PCAL9535A import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -58,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class PCAL9535ASwitch(SwitchDevice): +class PCAL9535ASwitch(SwitchEntity): """Representation of a PCAL9535A output pin.""" def __init__(self, name, pin, invert_logic): diff --git a/homeassistant/components/pencom/switch.py b/homeassistant/components/pencom/switch.py index 5cd1f826629..0fcdd056b15 100644 --- a/homeassistant/components/pencom/switch.py +++ b/homeassistant/components/pencom/switch.py @@ -4,7 +4,7 @@ import logging from pencompy.pencompy import Pencompy import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv @@ -59,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs, True) -class PencomRelay(SwitchDevice): +class PencomRelay(SwitchEntity): """Representation of a pencom relay.""" def __init__(self, hub, board, addr, name): diff --git a/homeassistant/components/pi4ioe5v9xxxx/switch.py b/homeassistant/components/pi4ioe5v9xxxx/switch.py index a7c1e19074c..a147c7f310d 100644 --- a/homeassistant/components/pi4ioe5v9xxxx/switch.py +++ b/homeassistant/components/pi4ioe5v9xxxx/switch.py @@ -4,7 +4,7 @@ import logging from pi4ioe5v9xxxx import pi4ioe5v9xxxx # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class Pi4ioe5v9Switch(SwitchDevice): +class Pi4ioe5v9Switch(SwitchEntity): """Representation of a pi4ioe5v9 IO expansion IO.""" def __init__(self, name, pin, invert_logic): diff --git a/homeassistant/components/pilight/switch.py b/homeassistant/components/pilight/switch.py index 0700b14e953..8fb2229f55c 100644 --- a/homeassistant/components/pilight/switch.py +++ b/homeassistant/components/pilight/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_SWITCHES import homeassistant.helpers.config_validation as cv @@ -27,5 +27,5 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class PilightSwitch(PilightBaseDevice, SwitchDevice): +class PilightSwitch(PilightBaseDevice, SwitchEntity): """Representation of a Pilight switch.""" diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index ec1adc7641b..45577fcd674 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -7,7 +7,7 @@ import socket import voluptuous as vol from homeassistant import util -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv @@ -137,7 +137,7 @@ class PAServer: return -1 -class PALoopbackSwitch(SwitchDevice): +class PALoopbackSwitch(SwitchEntity): """Representation the presence or absence of a PA loopback module.""" def __init__(self, hass, name, pa_server, sink_name, source_name): diff --git a/homeassistant/components/qwikswitch/__init__.py b/homeassistant/components/qwikswitch/__init__.py index 850429c1752..eea02fb9f54 100644 --- a/homeassistant/components/qwikswitch/__init__.py +++ b/homeassistant/components/qwikswitch/__init__.py @@ -103,7 +103,7 @@ class QSToggleEntity(QSEntity): Implemented: - QSLight extends QSToggleEntity and Light[2] (ToggleEntity[1]) - - QSSwitch extends QSToggleEntity and SwitchDevice[3] (ToggleEntity[1]) + - QSSwitch extends QSToggleEntity and SwitchEntity[3] (ToggleEntity[1]) [1] /helpers/entity.py [2] /components/light/__init__.py diff --git a/homeassistant/components/qwikswitch/switch.py b/homeassistant/components/qwikswitch/switch.py index 2d970a59a2a..61ef13f9e7a 100644 --- a/homeassistant/components/qwikswitch/switch.py +++ b/homeassistant/components/qwikswitch/switch.py @@ -1,5 +1,5 @@ """Support for Qwikswitch relays.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN as QWIKSWITCH, QSToggleEntity @@ -14,5 +14,5 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): add_entities(devs) -class QSSwitch(QSToggleEntity, SwitchDevice): +class QSSwitch(QSToggleEntity, SwitchEntity): """Switch based on a Qwikswitch relay module.""" diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 764f87e924c..f9cfa6ab122 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -3,7 +3,7 @@ from abc import abstractmethod from datetime import timedelta import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -79,7 +79,7 @@ def _create_entities(hass, config_entry): return entities -class RachioSwitch(RachioDevice, SwitchDevice): +class RachioSwitch(RachioDevice, SwitchEntity): """Represent a Rachio state that can be toggled.""" def __init__(self, controller, poll=True): diff --git a/homeassistant/components/rainbird/switch.py b/homeassistant/components/rainbird/switch.py index 7f589401e3c..1fd162c07b7 100644 --- a/homeassistant/components/rainbird/switch.py +++ b/homeassistant/components/rainbird/switch.py @@ -5,7 +5,7 @@ import logging from pyrainbird import AvailableStations, RainbirdController import voluptuous as vol -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import ATTR_ENTITY_ID, CONF_FRIENDLY_NAME, CONF_TRIGGER_TIME from homeassistant.helpers import config_validation as cv @@ -67,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class RainBirdSwitch(SwitchDevice): +class RainBirdSwitch(SwitchEntity): """Representation of a Rain Bird switch.""" def __init__(self, controller: RainbirdController, zone, time, name): diff --git a/homeassistant/components/raincloud/switch.py b/homeassistant/components/raincloud/switch.py index f85ed884ca9..d6733412cac 100644 --- a/homeassistant/components/raincloud/switch.py +++ b/homeassistant/components/raincloud/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -45,7 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class RainCloudSwitch(RainCloudEntity, SwitchDevice): +class RainCloudSwitch(RainCloudEntity, SwitchEntity): """A switch implementation for raincloud device.""" def __init__(self, default_watering_timer, *args): diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index b93f607f853..936aa8f1f0e 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -4,7 +4,7 @@ import logging from regenmaschine.errors import RequestError -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import ATTR_ID from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -111,7 +111,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities, True) -class RainMachineSwitch(RainMachineEntity, SwitchDevice): +class RainMachineSwitch(RainMachineEntity, SwitchEntity): """A class to represent a generic RainMachine switch.""" def __init__(self, rainmachine, switch_data): diff --git a/homeassistant/components/raspyrfm/switch.py b/homeassistant/components/raspyrfm/switch.py index ec07119b96a..cd4e28e1f10 100644 --- a/homeassistant/components/raspyrfm/switch.py +++ b/homeassistant/components/raspyrfm/switch.py @@ -12,7 +12,7 @@ from raspyrfm_client.device_implementations.gateway.manufacturer.gateway_constan from raspyrfm_client.device_implementations.manufacturer_constants import Manufacturer import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -87,7 +87,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switch_entities) -class RaspyRFMSwitch(SwitchDevice): +class RaspyRFMSwitch(SwitchEntity): """Representation of a RaspyRFM switch.""" def __init__(self, raspyrfm_client, name: str, gateway, controlunit): diff --git a/homeassistant/components/recswitch/switch.py b/homeassistant/components/recswitch/switch.py index c242f23dfdd..9c6ac8f9508 100644 --- a/homeassistant/components/recswitch/switch.py +++ b/homeassistant/components/recswitch/switch.py @@ -5,7 +5,7 @@ import logging from pyrecswitch import RSNetwork, RSNetworkError import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -40,7 +40,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([RecSwitchSwitch(device, device_name, mac_address)]) -class RecSwitchSwitch(SwitchDevice): +class RecSwitchSwitch(SwitchEntity): """Representation of a recswitch device.""" def __init__(self, device, device_name, mac_address): diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index 84d503bb09e..ab880201072 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -6,7 +6,7 @@ import aiohttp import async_timeout import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HEADERS, CONF_METHOD, @@ -109,7 +109,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _LOGGER.error("No route to resource/endpoint: %s", resource) -class RestSwitch(SwitchDevice): +class RestSwitch(SwitchEntity): """Representation of a switch that can be toggled using REST.""" def __init__( diff --git a/homeassistant/components/rflink/switch.py b/homeassistant/components/rflink/switch.py index 83c335f0f03..266f9a4625e 100644 --- a/homeassistant/components/rflink/switch.py +++ b/homeassistant/components/rflink/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -69,5 +69,5 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices_from_config(config)) -class RflinkSwitch(SwitchableRflinkDevice, SwitchDevice): +class RflinkSwitch(SwitchableRflinkDevice, SwitchEntity): """Representation of a Rflink switch.""" diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index 05e4a37ab44..960dc7dd33a 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -4,7 +4,7 @@ import logging import RFXtrx as rfxtrxmod import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME, STATE_ON from homeassistant.helpers import config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity @@ -68,7 +68,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): RECEIVED_EVT_SUBSCRIBERS.append(switch_update) -class RfxtrxSwitch(RfxtrxDevice, SwitchDevice, RestoreEntity): +class RfxtrxSwitch(RfxtrxDevice, SwitchEntity, RestoreEntity): """Representation of a RFXtrx switch.""" async def async_added_to_hass(self): diff --git a/homeassistant/components/ring/switch.py b/homeassistant/components/ring/switch.py index e2f1882adf6..09627b50965 100644 --- a/homeassistant/components/ring/switch.py +++ b/homeassistant/components/ring/switch.py @@ -4,7 +4,7 @@ import logging import requests -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback import homeassistant.util.dt as dt_util @@ -36,7 +36,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(switches) -class BaseRingSwitch(RingEntityMixin, SwitchDevice): +class BaseRingSwitch(RingEntityMixin, SwitchEntity): """Represents a switch for controlling an aspect of a ring device.""" def __init__(self, config_entry_id, device, device_type): diff --git a/homeassistant/components/rpi_rf/switch.py b/homeassistant/components/rpi_rf/switch.py index 5c09111c1cb..78c2153a7b3 100644 --- a/homeassistant/components/rpi_rf/switch.py +++ b/homeassistant/components/rpi_rf/switch.py @@ -5,7 +5,7 @@ from threading import RLock import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME, CONF_SWITCHES, EVENT_HOMEASSISTANT_STOP import homeassistant.helpers.config_validation as cv @@ -73,7 +73,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: rfdevice.cleanup()) -class RPiRFSwitch(SwitchDevice): +class RPiRFSwitch(SwitchEntity): """Representation of a GPIO RF switch.""" def __init__( diff --git a/homeassistant/components/satel_integra/switch.py b/homeassistant/components/satel_integra/switch.py index 46f4de2784f..e747b2474ae 100644 --- a/homeassistant/components/satel_integra/switch.py +++ b/homeassistant/components/satel_integra/switch.py @@ -1,7 +1,7 @@ """Support for Satel Integra modifiable outputs represented as switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -39,7 +39,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) -class SatelIntegraSwitch(SwitchDevice): +class SatelIntegraSwitch(SwitchEntity): """Representation of an Satel switch.""" def __init__(self, controller, device_number, device_name, code): diff --git a/homeassistant/components/scsgate/switch.py b/homeassistant/components/scsgate/switch.py index d5c85a5a84f..da69eb0b058 100644 --- a/homeassistant/components/scsgate/switch.py +++ b/homeassistant/components/scsgate/switch.py @@ -5,7 +5,7 @@ from scsgate.messages import ScenarioTriggeredMessage, StateMessage from scsgate.tasks import ToggleStatusTask import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -80,7 +80,7 @@ def _setup_scenario_switches(logger, config, scsgate, hass): scsgate.add_device(switch) -class SCSGateSwitch(SwitchDevice): +class SCSGateSwitch(SwitchEntity): """Representation of a SCSGate switch.""" def __init__(self, scs_id, name, logger, scsgate): diff --git a/homeassistant/components/skybell/switch.py b/homeassistant/components/skybell/switch.py index 03ea74a2340..97a1d2a4c00 100644 --- a/homeassistant/components/skybell/switch.py +++ b/homeassistant/components/skybell/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -41,7 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(sensors, True) -class SkybellSwitch(SkybellDevice, SwitchDevice): +class SkybellSwitch(SkybellDevice, SwitchEntity): """A switch implementation for Skybell devices.""" def __init__(self, device, switch_type): diff --git a/homeassistant/components/smappee/switch.py b/homeassistant/components/smappee/switch.py index cb7a1e8a395..6f6481d65f9 100644 --- a/homeassistant/components/smappee/switch.py +++ b/homeassistant/components/smappee/switch.py @@ -1,7 +1,7 @@ """Support for interacting with Smappee Comport Plugs.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DATA_SMAPPEE @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(dev) -class SmappeeSwitch(SwitchDevice): +class SmappeeSwitch(SwitchEntity): """Representation of a Smappee Comport Plug.""" def __init__(self, smappee, name, location_id, switch_id): diff --git a/homeassistant/components/smartthings/switch.py b/homeassistant/components/smartthings/switch.py index eb9c9c90c4b..ff70648ddcf 100644 --- a/homeassistant/components/smartthings/switch.py +++ b/homeassistant/components/smartthings/switch.py @@ -3,7 +3,7 @@ from typing import Optional, Sequence from pysmartthings import Attribute, Capability -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -29,7 +29,7 @@ def get_capabilities(capabilities: Sequence[str]) -> Optional[Sequence[str]]: return None -class SmartThingsSwitch(SmartThingsEntity, SwitchDevice): +class SmartThingsSwitch(SmartThingsEntity, SwitchEntity): """Define a SmartThings switch.""" async def async_turn_off(self, **kwargs) -> None: diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py index 578b97c801e..18effc563bb 100644 --- a/homeassistant/components/snmp/switch.py +++ b/homeassistant/components/snmp/switch.py @@ -16,7 +16,7 @@ from pysnmp.hlapi.asyncio import ( ) import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -126,7 +126,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class SnmpSwitch(SwitchDevice): +class SnmpSwitch(SwitchEntity): """Representation of a SNMP switch.""" def __init__( diff --git a/homeassistant/components/somfy/switch.py b/homeassistant/components/somfy/switch.py index bc31d68ec1d..e96c91ecaea 100644 --- a/homeassistant/components/somfy/switch.py +++ b/homeassistant/components/somfy/switch.py @@ -2,7 +2,7 @@ from pymfy.api.devices.camera_protect import CameraProtect from pymfy.api.devices.category import Category -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import API, DEVICES, DOMAIN, SomfyEntity @@ -23,7 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(await hass.async_add_executor_job(get_shutters), True) -class SomfyCameraShutter(SomfyEntity, SwitchDevice): +class SomfyCameraShutter(SomfyEntity, SwitchEntity): """Representation of a Somfy Camera Shutter device.""" def __init__(self, device, api): diff --git a/homeassistant/components/sony_projector/switch.py b/homeassistant/components/sony_projector/switch.py index e68bed34cfa..e14d74dd2c0 100644 --- a/homeassistant/components/sony_projector/switch.py +++ b/homeassistant/components/sony_projector/switch.py @@ -4,7 +4,7 @@ import logging import pysdcp import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return True -class SonyProjector(SwitchDevice): +class SonyProjector(SwitchEntity): """Represents a Sony Projector as a switch.""" def __init__(self, sdcp_connection, name): diff --git a/homeassistant/components/spider/switch.py b/homeassistant/components/spider/switch.py index b02c38c84aa..58a45cf7b4d 100644 --- a/homeassistant/components/spider/switch.py +++ b/homeassistant/components/spider/switch.py @@ -1,7 +1,7 @@ """Support for Spider switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN as SPIDER_DOMAIN @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class SpiderPowerPlug(SwitchDevice): +class SpiderPowerPlug(SwitchEntity): """Representation of a Spider Power Plug.""" def __init__(self, api, power_plug): diff --git a/homeassistant/components/starline/switch.py b/homeassistant/components/starline/switch.py index 920fe686d9a..c50a7bb4973 100644 --- a/homeassistant/components/starline/switch.py +++ b/homeassistant/components/starline/switch.py @@ -1,5 +1,5 @@ """Support for StarLine switch.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from .account import StarlineAccount, StarlineDevice from .const import DOMAIN @@ -30,7 +30,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class StarlineSwitch(StarlineEntity, SwitchDevice): +class StarlineSwitch(StarlineEntity, SwitchEntity): """Representation of a StarLine switch.""" def __init__( diff --git a/homeassistant/components/supla/switch.py b/homeassistant/components/supla/switch.py index 556c1b69a53..61f218b75d9 100644 --- a/homeassistant/components/supla/switch.py +++ b/homeassistant/components/supla/switch.py @@ -3,7 +3,7 @@ import logging from pprint import pformat from homeassistant.components.supla import SuplaChannel -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity _LOGGER = logging.getLogger(__name__) @@ -18,7 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SuplaSwitch(device) for device in discovery_info]) -class SuplaSwitch(SuplaChannel, SwitchDevice): +class SuplaSwitch(SuplaChannel, SwitchEntity): """Representation of a Supla Switch.""" def turn_on(self, **kwargs): diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 3884b90c464..1d9b54a0424 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -78,7 +78,7 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class SwitchDevice(ToggleEntity): +class SwitchEntity(ToggleEntity): """Representation of a switch.""" @property @@ -112,3 +112,15 @@ class SwitchDevice(ToggleEntity): def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" return None + + +class SwitchDevice(SwitchEntity): + """Representation of a switch (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "SwitchDevice is deprecated, modify %s to extend SwitchEntity", + cls.__name__, + ) diff --git a/homeassistant/components/switchbot/switch.py b/homeassistant/components/switchbot/switch.py index f0cbecc8968..6d32f8cfd10 100644 --- a/homeassistant/components/switchbot/switch.py +++ b/homeassistant/components/switchbot/switch.py @@ -6,7 +6,7 @@ from typing import Any, Dict import switchbot import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_MAC, CONF_NAME, CONF_PASSWORD import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([SwitchBot(mac_addr, name, password)]) -class SwitchBot(SwitchDevice, RestoreEntity): +class SwitchBot(SwitchEntity, RestoreEntity): """Representation of a Switchbot.""" def __init__(self, mac, name, password) -> None: diff --git a/homeassistant/components/switcher_kis/switch.py b/homeassistant/components/switcher_kis/switch.py index ea32183b511..c2254968901 100644 --- a/homeassistant/components/switcher_kis/switch.py +++ b/homeassistant/components/switcher_kis/switch.py @@ -12,7 +12,7 @@ from aioswitcher.consts import ( WAITING_TEXT, ) -from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchDevice +from homeassistant.components.switch import ATTR_CURRENT_POWER_W, SwitchEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType @@ -53,7 +53,7 @@ async def async_setup_platform( async_add_entities([SwitcherControl(hass.data[DOMAIN][DATA_DEVICE])]) -class SwitcherControl(SwitchDevice): +class SwitcherControl(SwitchEntity): """Home Assistant switch entity.""" def __init__(self, device_data: "SwitcherV2Device") -> None: diff --git a/homeassistant/components/switchmate/switch.py b/homeassistant/components/switchmate/switch.py index 268115434cf..e09e42fe75b 100644 --- a/homeassistant/components/switchmate/switch.py +++ b/homeassistant/components/switchmate/switch.py @@ -6,7 +6,7 @@ import logging import switchmate import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_MAC, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None) -> None: add_entities([SwitchmateEntity(mac_addr, name, flip_on_off)], True) -class SwitchmateEntity(SwitchDevice): +class SwitchmateEntity(SwitchEntity): """Representation of a Switchmate.""" def __init__(self, mac, name, flip_on_off) -> None: diff --git a/homeassistant/components/tahoma/switch.py b/homeassistant/components/tahoma/switch.py index 9f98e711ac9..13aa70c66d3 100644 --- a/homeassistant/components/tahoma/switch.py +++ b/homeassistant/components/tahoma/switch.py @@ -1,7 +1,7 @@ """Support for Tahoma switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -22,7 +22,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class TahomaSwitch(TahomaDevice, SwitchDevice): +class TahomaSwitch(TahomaDevice, SwitchEntity): """Representation a Tahoma Switch.""" def __init__(self, tahoma_device, controller): diff --git a/homeassistant/components/telnet/switch.py b/homeassistant/components/telnet/switch.py index a99fe044c46..e4d93d9f685 100644 --- a/homeassistant/components/telnet/switch.py +++ b/homeassistant/components/telnet/switch.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.switch import ( ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import ( CONF_COMMAND_OFF, @@ -81,7 +81,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class TelnetSwitch(SwitchDevice): +class TelnetSwitch(SwitchEntity): """Representation of a switch that can be toggled using telnet commands.""" def __init__( diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index f96ed5479b9..0ec0eca553a 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.switch import ( ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -96,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(switches) -class SwitchTemplate(SwitchDevice): +class SwitchTemplate(SwitchEntity): """Representation of a Template switch.""" def __init__( diff --git a/homeassistant/components/tesla/switch.py b/homeassistant/components/tesla/switch.py index 716836821c4..5e9c2aa9031 100644 --- a/homeassistant/components/tesla/switch.py +++ b/homeassistant/components/tesla/switch.py @@ -1,7 +1,7 @@ """Support for Tesla charger switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON from . import DOMAIN as TESLA_DOMAIN, TeslaDevice @@ -24,7 +24,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): async_add_entities(entities, True) -class ChargerSwitch(TeslaDevice, SwitchDevice): +class ChargerSwitch(TeslaDevice, SwitchEntity): """Representation of a Tesla charger switch.""" def __init__(self, tesla_device, controller, config_entry): @@ -54,7 +54,7 @@ class ChargerSwitch(TeslaDevice, SwitchDevice): self._state = STATE_ON if self.tesla_device.is_charging() else STATE_OFF -class RangeSwitch(TeslaDevice, SwitchDevice): +class RangeSwitch(TeslaDevice, SwitchEntity): """Representation of a Tesla max range charging switch.""" def __init__(self, tesla_device, controller, config_entry): @@ -84,7 +84,7 @@ class RangeSwitch(TeslaDevice, SwitchDevice): self._state = bool(self.tesla_device.is_maxrange()) -class UpdateSwitch(TeslaDevice, SwitchDevice): +class UpdateSwitch(TeslaDevice, SwitchEntity): """Representation of a Tesla update switch.""" def __init__(self, tesla_device, controller, config_entry): @@ -118,7 +118,7 @@ class UpdateSwitch(TeslaDevice, SwitchDevice): self._state = bool(self.controller.get_updates(car_id)) -class SentryModeSwitch(TeslaDevice, SwitchDevice): +class SentryModeSwitch(TeslaDevice, SwitchEntity): """Representation of a Tesla sentry mode switch.""" async def async_turn_on(self, **kwargs): diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 59d993477df..344f9cd96b0 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -7,7 +7,7 @@ from pyHS100 import SmartDeviceException, SmartPlug from homeassistant.components.switch import ( ATTR_CURRENT_POWER_W, ATTR_TODAY_ENERGY_KWH, - SwitchDevice, + SwitchEntity, ) from homeassistant.const import ATTR_VOLTAGE import homeassistant.helpers.device_registry as dr @@ -43,7 +43,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry, async_add_ent return True -class SmartPlugSwitch(SwitchDevice): +class SmartPlugSwitch(SwitchEntity): """Representation of a TPLink Smart Plug switch.""" def __init__(self, smartplug: SmartPlug): diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index fffbf320c7e..cf23ffeb445 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -1,5 +1,5 @@ """Support for IKEA Tradfri switches.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from .base_class import TradfriBaseDevice from .const import CONF_GATEWAY_ID, KEY_API, KEY_GATEWAY @@ -20,7 +20,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class TradfriSwitch(TradfriBaseDevice, SwitchDevice): +class TradfriSwitch(TradfriBaseDevice, SwitchEntity): """The platform class required by Home Assistant.""" def __init__(self, device, api, gateway_id): diff --git a/homeassistant/components/tuya/switch.py b/homeassistant/components/tuya/switch.py index 17cf5ae873a..30684c16da5 100644 --- a/homeassistant/components/tuya/switch.py +++ b/homeassistant/components/tuya/switch.py @@ -1,5 +1,5 @@ """Support for Tuya switches.""" -from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice +from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity from . import DATA_TUYA, TuyaDevice @@ -21,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class TuyaSwitch(TuyaDevice, SwitchDevice): +class TuyaSwitch(TuyaDevice, SwitchEntity): """Tuya Switch Device.""" def __init__(self, tuya): diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index b562b8bd746..ea39c82853a 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -1,7 +1,7 @@ """Support for devices connected to UniFi POE.""" import logging -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.restore_state import RestoreEntity @@ -125,7 +125,7 @@ def add_entities(controller, async_add_entities, previously_known_poe_clients): async_add_entities(switches) -class UniFiPOEClientSwitch(UniFiClient, SwitchDevice, RestoreEntity): +class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): """Representation of a client that uses POE.""" DOMAIN = DOMAIN @@ -220,7 +220,7 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchDevice, RestoreEntity): await self.async_remove() -class UniFiBlockClientSwitch(UniFiClient, SwitchDevice): +class UniFiBlockClientSwitch(UniFiClient, SwitchEntity): """Representation of a blockable client.""" DOMAIN = DOMAIN diff --git a/homeassistant/components/upcloud/switch.py b/homeassistant/components/upcloud/switch.py index 5cb1d86671e..1d0984cdddb 100644 --- a/homeassistant/components/upcloud/switch.py +++ b/homeassistant/components/upcloud/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import STATE_OFF import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices, True) -class UpCloudSwitch(UpCloudServerEntity, SwitchDevice): +class UpCloudSwitch(UpCloudServerEntity, SwitchEntity): """Representation of an UpCloud server switch.""" def turn_on(self, **kwargs): diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 64d4b7c17f8..91746b1513e 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -3,7 +3,7 @@ import logging from velbus.util import VelbusException -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import VelbusEntity from .const import DOMAIN @@ -22,7 +22,7 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(entities) -class VelbusSwitch(VelbusEntity, SwitchDevice): +class VelbusSwitch(VelbusEntity, SwitchEntity): """Representation of a switch.""" @property diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index a7ae6d45573..0a9a94d6372 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -5,7 +5,7 @@ from typing import Callable, List from homeassistant.components.switch import ( DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT, - SwitchDevice, + SwitchEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -33,7 +33,7 @@ async def async_setup_entry( ) -class VeraSwitch(VeraDevice, SwitchDevice): +class VeraSwitch(VeraDevice, SwitchEntity): """Representation of a Vera Switch.""" def __init__(self, vera_device, controller): diff --git a/homeassistant/components/verisure/switch.py b/homeassistant/components/verisure/switch.py index 2df250303c5..8025b22f402 100644 --- a/homeassistant/components/verisure/switch.py +++ b/homeassistant/components/verisure/switch.py @@ -2,7 +2,7 @@ import logging from time import monotonic -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import CONF_SMARTPLUGS, HUB as hub @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class VerisureSmartplug(SwitchDevice): +class VerisureSmartplug(SwitchEntity): """Representation of a Verisure smartplug.""" def __init__(self, device_id): diff --git a/homeassistant/components/versasense/switch.py b/homeassistant/components/versasense/switch.py index 4b44cb7aa2a..78eadffea34 100644 --- a/homeassistant/components/versasense/switch.py +++ b/homeassistant/components/versasense/switch.py @@ -1,7 +1,7 @@ """Support for VersaSense actuator peripheral.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN from .const import ( @@ -42,7 +42,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(actuator_list) -class VActuator(SwitchDevice): +class VActuator(SwitchEntity): """Representation of an Actuator.""" def __init__(self, peripheral, parent_name, unit, measurement, consumer): diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 6ab5c0c4368..fb6e83227e9 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -1,7 +1,7 @@ """Support for Etekcity VeSync switches.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -55,7 +55,7 @@ def _async_setup_entities(devices, async_add_entities): async_add_entities(dev_list, update_before_add=True) -class VeSyncSwitchHA(VeSyncDevice, SwitchDevice): +class VeSyncSwitchHA(VeSyncDevice, SwitchEntity): """Representation of a VeSync switch.""" def __init__(self, plug): @@ -90,7 +90,7 @@ class VeSyncSwitchHA(VeSyncDevice, SwitchDevice): self.smartplug.update_energy() -class VeSyncLightSwitch(VeSyncDevice, SwitchDevice): +class VeSyncLightSwitch(VeSyncDevice, SwitchEntity): """Handle representation of VeSync Light Switch.""" def __init__(self, switch): diff --git a/homeassistant/components/vultr/switch.py b/homeassistant/components/vultr/switch.py index da79652ec5b..a9c43717a71 100644 --- a/homeassistant/components/vultr/switch.py +++ b/homeassistant/components/vultr/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv @@ -50,7 +50,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([VultrSwitch(vultr, subscription, name)], True) -class VultrSwitch(SwitchDevice): +class VultrSwitch(SwitchEntity): """Representation of a Vultr subscription switch.""" def __init__(self, vultr, subscription, name): diff --git a/homeassistant/components/wake_on_lan/switch.py b/homeassistant/components/wake_on_lan/switch.py index 8200a0309fa..e3af8f146f1 100644 --- a/homeassistant/components/wake_on_lan/switch.py +++ b/homeassistant/components/wake_on_lan/switch.py @@ -6,7 +6,7 @@ import subprocess as sp import voluptuous as vol import wakeonlan -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_BROADCAST_ADDRESS, CONF_HOST, CONF_MAC, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class WolSwitch(SwitchDevice): +class WolSwitch(SwitchEntity): """Representation of a wake on lan switch.""" def __init__(self, hass, name, host, mac_address, off_action, broadcast_address): diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 4b6d99da200..203c495e974 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -5,7 +5,7 @@ import logging import async_timeout -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import convert @@ -47,7 +47,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class WemoSwitch(SwitchDevice): +class WemoSwitch(SwitchEntity): """Representation of a WeMo switch.""" def __init__(self, device): diff --git a/homeassistant/components/wirelesstag/switch.py b/homeassistant/components/wirelesstag/switch.py index 79242394f7f..9027f92aec0 100644 --- a/homeassistant/components/wirelesstag/switch.py +++ b/homeassistant/components/wirelesstag/switch.py @@ -3,7 +3,7 @@ import logging import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -49,7 +49,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches, True) -class WirelessTagSwitch(WirelessTagBaseSensor, SwitchDevice): +class WirelessTagSwitch(WirelessTagBaseSensor, SwitchEntity): """A switch implementation for Wireless Sensor Tags.""" def __init__(self, api, tag, switch_type): diff --git a/homeassistant/components/wled/switch.py b/homeassistant/components/wled/switch.py index 85a1f261d94..440819927b1 100644 --- a/homeassistant/components/wled/switch.py +++ b/homeassistant/components/wled/switch.py @@ -2,7 +2,7 @@ import logging from typing import Any, Callable, Dict, List, Optional -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType @@ -37,7 +37,7 @@ async def async_setup_entry( async_add_entities(switches, True) -class WLEDSwitch(WLEDDeviceEntity, SwitchDevice): +class WLEDSwitch(WLEDDeviceEntity, SwitchEntity): """Defines a WLED switch.""" def __init__( diff --git a/homeassistant/components/xiaomi_aqara/switch.py b/homeassistant/components/xiaomi_aqara/switch.py index 90201e9c452..e711eab46fb 100644 --- a/homeassistant/components/xiaomi_aqara/switch.py +++ b/homeassistant/components/xiaomi_aqara/switch.py @@ -1,7 +1,7 @@ """Support for Xiaomi Aqara binary sensors.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import PY_XIAOMI_GATEWAY, XiaomiDevice @@ -79,7 +79,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devices) -class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice): +class XiaomiGenericSwitch(XiaomiDevice, SwitchEntity): """Representation of a XiaomiPlug.""" def __init__(self, device, name, data_key, supports_power_consumption, xiaomi_hub): diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index c66f9b745f5..d8552244ce8 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -13,7 +13,7 @@ from miio import ( # pylint: disable=import-error from miio.powerstrip import PowerMode # pylint: disable=import-error import voluptuous as vol -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_MODE, @@ -217,7 +217,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) -class XiaomiPlugGenericSwitch(SwitchDevice): +class XiaomiPlugGenericSwitch(SwitchEntity): """Representation of a Xiaomi Plug Generic.""" def __init__(self, name, plug, model, unique_id): diff --git a/homeassistant/components/zha/switch.py b/homeassistant/components/zha/switch.py index 328d9959ad2..9a7fc7aa6b0 100644 --- a/homeassistant/components/zha/switch.py +++ b/homeassistant/components/zha/switch.py @@ -6,7 +6,7 @@ from typing import Any, List from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.foundation import Status -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.const import STATE_ON, STATE_UNAVAILABLE from homeassistant.core import State, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -41,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) -class BaseSwitch(SwitchDevice): +class BaseSwitch(SwitchEntity): """Common base class for zha switches.""" def __init__(self, *args, **kwargs): diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/zigbee/switch.py index e29d2c045df..f5b73f5d328 100644 --- a/homeassistant/components/zigbee/switch.py +++ b/homeassistant/components/zigbee/switch.py @@ -1,7 +1,7 @@ """Support for Zigbee switches.""" import voluptuous as vol -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig @@ -20,5 +20,5 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([ZigBeeSwitch(ZigBeeDigitalOutConfig(config), zigbee_device)]) -class ZigBeeSwitch(ZigBeeDigitalOut, SwitchDevice): +class ZigBeeSwitch(ZigBeeDigitalOut, SwitchEntity): """Representation of a Zigbee Digital Out device.""" diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index 5eaf2ed4901..0428ddbf888 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from zoneminder.monitor import MonitorState -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON import homeassistant.helpers.config_validation as cv @@ -38,7 +38,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(switches) -class ZMSwitchMonitors(SwitchDevice): +class ZMSwitchMonitors(SwitchEntity): """Representation of a ZoneMinder switch.""" icon = "mdi:record-rec" diff --git a/homeassistant/components/zwave/switch.py b/homeassistant/components/zwave/switch.py index 4956e99a40e..c5770bf7702 100644 --- a/homeassistant/components/zwave/switch.py +++ b/homeassistant/components/zwave/switch.py @@ -2,7 +2,7 @@ import logging import time -from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -27,7 +27,7 @@ def get_device(values, **kwargs): return ZwaveSwitch(values) -class ZwaveSwitch(ZWaveDeviceEntity, SwitchDevice): +class ZwaveSwitch(ZWaveDeviceEntity, SwitchEntity): """Representation of a Z-Wave switch.""" def __init__(self, values): diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index 07ab1d19f55..7a14d3ac117 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -103,3 +103,13 @@ async def test_switch_context(hass, hass_admin_user): assert state2 is not None assert state.state != state2.state assert state2.context.user_id == hass_admin_user.id + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomSwitch(switch.SwitchDevice): + pass + + CustomSwitch() + assert "SwitchDevice is deprecated, modify CustomSwitch" in caplog.text From ca6243c18aef8a04eb2cbb70b02197bcc8e42111 Mon Sep 17 00:00:00 2001 From: rako77 Date: Sun, 26 Apr 2020 12:25:58 -0700 Subject: [PATCH 077/511] Add missing typing to Spotify (#34698) --- homeassistant/components/spotify/media_player.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index c708a9fc27e..1b74855c9f9 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -32,6 +32,7 @@ from homeassistant.const import ( STATE_PLAYING, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session from homeassistant.helpers.entity import Entity from homeassistant.util.dt import utc_from_timestamp @@ -93,7 +94,14 @@ def spotify_exception_handler(func): class SpotifyMediaPlayer(MediaPlayerEntity): """Representation of a Spotify controller.""" - def __init__(self, session, spotify: Spotify, me: dict, user_id: str, name: str): + def __init__( + self, + session: OAuth2Session, + spotify: Spotify, + me: dict, + user_id: str, + name: str, + ): """Initialize.""" self._id = user_id self._me = me From 1c84905bcd05d27d642f1e6a6a8c9ab553b45b2f Mon Sep 17 00:00:00 2001 From: Quentame Date: Sun, 26 Apr 2020 22:25:13 +0200 Subject: [PATCH 078/511] Bump python-synology to 0.7.1 (#34728) --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index c7abd809b87..f3fd52a74a5 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.0"], + "requirements": ["python-synology==0.7.1"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 906c658a631..3ab58b34e3d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1680,7 +1680,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.0 +python-synology==0.7.1 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 57bdfc76ef0..3cf7a633822 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -650,7 +650,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.0 +python-synology==0.7.1 # homeassistant.components.tado python-tado==0.8.1 From b6292d7c773c9e1d65ed6f1ff9d29a4bdb1928fb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 26 Apr 2020 22:53:02 +0200 Subject: [PATCH 079/511] Upgrade pillow to 7.1.2 (#34733) --- homeassistant/components/doods/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 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index 3fa20f2eea4..f363f46d2d7 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,6 +2,6 @@ "domain": "doods", "name": "DOODS - Distributed Outside Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==7.1.1"], + "requirements": ["pydoods==1.0.2", "pillow==7.1.2"], "codeowners": [] } diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index 89d25ca23b1..ffa659f979e 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==7.1.1"], + "requirements": ["pillow==7.1.2"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 3b9df1a4e64..eaa813cae95 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==7.1.1", "pyzbar==0.1.7"], + "requirements": ["pillow==7.1.2", "pyzbar==0.1.7"], "codeowners": [] } diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index ef155676d1f..352b96b5f22 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==7.1.1"], + "requirements": ["pillow==7.1.2"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index 78f38f5316f..1ad7effdf0e 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==7.1.1", "simplehound==0.3"], + "requirements": ["pillow==7.1.2", "simplehound==0.3"], "codeowners": ["@robmarkcole"] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 2ea292e8ff5..3e1fe5b1f5c 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -6,7 +6,7 @@ "tensorflow==1.13.2", "numpy==1.18.2", "protobuf==3.6.1", - "pillow==7.1.1" + "pillow==7.1.2" ], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 3ab58b34e3d..b5a1b2faf6a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1045,7 +1045,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==7.1.1 +pillow==7.1.2 # homeassistant.components.dominos pizzapi==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3cf7a633822..0b1fe306ada 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -408,7 +408,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==7.1.1 +pillow==7.1.2 # homeassistant.components.plex plexapi==3.4.0 From b85bd2cb99e1f5fc72209c22892637c38d8d61f1 Mon Sep 17 00:00:00 2001 From: Thomas Schamm Date: Sun, 26 Apr 2020 22:54:02 +0200 Subject: [PATCH 080/511] Create a unique_id for velux cover (#34668) --- homeassistant/components/velux/cover.py | 5 +++++ homeassistant/components/velux/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 52d2e497b7d..901946b3245 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -46,6 +46,11 @@ class VeluxCover(CoverEntity): """Store register state change callback.""" self.async_register_callbacks() + @property + def unique_id(self): + """Return the unique ID of this cover.""" + return self.node.serial_number + @property def name(self): """Return the name of the Velux device.""" diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index d67e29af693..e6747060da6 100644 --- a/homeassistant/components/velux/manifest.json +++ b/homeassistant/components/velux/manifest.json @@ -2,6 +2,6 @@ "domain": "velux", "name": "Velux", "documentation": "https://www.home-assistant.io/integrations/velux", - "requirements": ["pyvlx==0.2.12"], + "requirements": ["pyvlx==0.2.14"], "codeowners": ["@Julius2342"] } diff --git a/requirements_all.txt b/requirements_all.txt index b5a1b2faf6a..724f59f874d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1756,7 +1756,7 @@ pyvesync==1.1.0 pyvizio==0.1.47 # homeassistant.components.velux -pyvlx==0.2.12 +pyvlx==0.2.14 # homeassistant.components.html5 pywebpush==1.9.2 From 22fdccba02cac18393e6bd4d4075d1d883a1163b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 26 Apr 2020 22:54:19 +0200 Subject: [PATCH 081/511] Upgrade pytest-sugar to 0.9.3 (#34726) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 8a9bc7ddf84..bb0dc4d9e02 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -13,7 +13,7 @@ astroid==2.3.3 pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.8.1 -pytest-sugar==0.9.2 +pytest-sugar==0.9.3 pytest-timeout==1.3.3 pytest==5.3.5 requests_mock==1.7.0 From de3f0c788c04abf4b6c3039a8f2bb4d8f17d7344 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 26 Apr 2020 22:56:59 +0200 Subject: [PATCH 082/511] Upgrade pytest-timeout to v1.3.4 (#34609) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index bb0dc4d9e02..a8528c35729 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -14,7 +14,7 @@ pylint-strict-informational==0.1 pytest-aiohttp==0.3.0 pytest-cov==2.8.1 pytest-sugar==0.9.3 -pytest-timeout==1.3.3 +pytest-timeout==1.3.4 pytest==5.3.5 requests_mock==1.7.0 responses==0.10.6 From 13c8c2e841280acc0466c5184f308369977e92e3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Apr 2020 16:57:29 -0700 Subject: [PATCH 083/511] Add unique ID to TRADFRI (#34745) --- .../components/tradfri/config_flow.py | 30 +++++++------ .../components/tradfri/manifest.json | 1 - homeassistant/generated/zeroconf.py | 3 -- tests/components/tradfri/test_config_flow.py | 43 ++++++++++++++++--- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 048541b5402..1663ba675a3 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -1,6 +1,5 @@ """Config flow for Tradfri.""" import asyncio -from collections import OrderedDict from uuid import uuid4 import async_timeout @@ -70,7 +69,7 @@ class FlowHandler(config_entries.ConfigFlow): else: user_input = {} - fields = OrderedDict() + fields = {} if self._host is None: fields[vol.Required(CONF_HOST, default=user_input.get(CONF_HOST))] = str @@ -83,25 +82,28 @@ class FlowHandler(config_entries.ConfigFlow): step_id="auth", data_schema=vol.Schema(fields), errors=errors ) - async def async_step_zeroconf(self, user_input): - """Handle zeroconf discovery.""" + async def async_step_homekit(self, user_input): + """Handle homekit discovery.""" + await self.async_set_unique_id(user_input["properties"]["id"]) + self._abort_if_unique_id_configured({CONF_HOST: user_input["host"]}) + host = user_input["host"] - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context["host"] = host - - if any(host == flow["context"]["host"] for flow in self._async_in_progress()): - return self.async_abort(reason="already_in_progress") - for entry in self._async_current_entries(): - if entry.data[CONF_HOST] == host: - return self.async_abort(reason="already_configured") + if entry.data[CONF_HOST] != host: + continue + + # Backwards compat, we update old entries + if not entry.unique_id: + self.hass.config_entries.async_update_entry( + entry, unique_id=user_input["properties"]["id"] + ) + + return self.async_abort(reason="already_configured") self._host = host return await self.async_step_auth() - async_step_homekit = async_step_zeroconf - async def async_step_import(self, user_input): """Import a config entry.""" for entry in self._async_current_entries(): diff --git a/homeassistant/components/tradfri/manifest.json b/homeassistant/components/tradfri/manifest.json index ce88766039b..12457975eee 100644 --- a/homeassistant/components/tradfri/manifest.json +++ b/homeassistant/components/tradfri/manifest.json @@ -7,6 +7,5 @@ "homekit": { "models": ["TRADFRI"] }, - "zeroconf": ["_coap._udp.local."], "codeowners": ["@ggravlingen"] } diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index d6e4965c235..fa0c5ad593a 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -10,9 +10,6 @@ ZEROCONF = { "axis", "doorbird" ], - "_coap._udp.local.": [ - "tradfri" - ], "_elg._tcp.local.": [ "elgato" ], diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 2a4a831575a..45e18be83d3 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -80,7 +80,9 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup): mock_auth.side_effect = lambda hass, host, code: {"host": host, "gateway_id": "bla"} flow = await hass.config_entries.flow.async_init( - "tradfri", context={"source": "zeroconf"}, data={"host": "123.123.123.123"} + "tradfri", + context={"source": "homekit"}, + data={"host": "123.123.123.123", "properties": {"id": "homekit-id"}}, ) result = await hass.config_entries.flow.async_configure( @@ -90,6 +92,7 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup): assert len(mock_entry_setup.mock_calls) == 1 assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == "homekit-id" assert result["result"].data == { "host": "123.123.123.123", "gateway_id": "bla", @@ -218,16 +221,23 @@ async def test_import_connection_legacy_no_groups( async def test_discovery_duplicate_aborted(hass): - """Test a duplicate discovery host is ignored.""" - MockConfigEntry(domain="tradfri", data={"host": "some-host"}).add_to_hass(hass) + """Test a duplicate discovery host aborts and updates existing entry.""" + entry = MockConfigEntry( + domain="tradfri", data={"host": "some-host"}, unique_id="homekit-id" + ) + entry.add_to_hass(hass) flow = await hass.config_entries.flow.async_init( - "tradfri", context={"source": "zeroconf"}, data={"host": "some-host"} + "tradfri", + context={"source": "homekit"}, + data={"host": "new-host", "properties": {"id": "homekit-id"}}, ) assert flow["type"] == data_entry_flow.RESULT_TYPE_ABORT assert flow["reason"] == "already_configured" + assert entry.data["host"] == "new-host" + async def test_import_duplicate_aborted(hass): """Test a duplicate import host is ignored.""" @@ -244,13 +254,34 @@ async def test_import_duplicate_aborted(hass): async def test_duplicate_discovery(hass, mock_auth, mock_entry_setup): """Test a duplicate discovery in progress is ignored.""" result = await hass.config_entries.flow.async_init( - "tradfri", context={"source": "zeroconf"}, data={"host": "123.123.123.123"} + "tradfri", + context={"source": "homekit"}, + data={"host": "123.123.123.123", "properties": {"id": "homekit-id"}}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM result2 = await hass.config_entries.flow.async_init( - "tradfri", context={"source": "zeroconf"}, data={"host": "123.123.123.123"} + "tradfri", + context={"source": "homekit"}, + data={"host": "123.123.123.123", "properties": {"id": "homekit-id"}}, ) assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_discovery_updates_unique_id(hass): + """Test a duplicate discovery host aborts and updates existing entry.""" + entry = MockConfigEntry(domain="tradfri", data={"host": "some-host"},) + entry.add_to_hass(hass) + + flow = await hass.config_entries.flow.async_init( + "tradfri", + context={"source": "homekit"}, + data={"host": "some-host", "properties": {"id": "homekit-id"}}, + ) + + assert flow["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert flow["reason"] == "already_configured" + + assert entry.unique_id == "homekit-id" From aa2bfbb541df795a44dd886835f844d96d91b1dc Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 27 Apr 2020 00:02:20 +0000 Subject: [PATCH 084/511] [ci skip] Translation update --- .../components/airvisual/translations/fr.json | 7 ++++ .../components/airvisual/translations/hi.json | 34 ++++++++++++++++++ .../components/atag/translations/fr.json | 1 + .../components/atag/translations/hi.json | 17 +++++++++ .../components/braviatv/translations/hi.json | 9 +++++ .../components/calendar/translations/hi.json | 3 +- .../components/camera/translations/hi.json | 3 +- .../components/deconz/translations/fr.json | 6 ++++ .../components/doorbird/translations/fr.json | 3 +- .../components/doorbird/translations/pl.json | 33 +++++++++++++++++ .../components/elkm1/translations/fr.json | 4 +++ .../components/flume/translations/fr.json | 4 +++ .../components/fritzbox/translations/fr.json | 4 ++- .../components/fritzbox/translations/hi.json | 7 ++++ .../components/group/translations/hi.json | 7 +++- .../components/hue/translations/fr.json | 15 +++++++- .../components/ipp/translations/pl.json | 7 ++++ .../islamic_prayer_times/translations/de.json | 3 +- .../media_player/translations/hi.json | 6 +++- .../components/nut/translations/de.json | 3 +- .../components/nut/translations/fr.json | 24 +++++++++++++ .../components/nws/translations/fr.json | 8 +++++ .../panasonic_viera/translations/fr.json | 11 +++++- .../panasonic_viera/translations/hi.json | 9 +++++ .../components/person/translations/hi.json | 8 +++++ .../components/roomba/translations/fr.json | 15 ++++++++ .../smartthings/translations/fr.json | 10 ++++++ .../synology_dsm/translations/ca.json | 1 + .../synology_dsm/translations/de.json | 1 + .../synology_dsm/translations/hi.json | 8 +++++ .../synology_dsm/translations/pl.json | 35 +++++++++++++++++++ .../synology_dsm/translations/ru.json | 1 + .../synology_dsm/translations/zh-Hant.json | 1 + .../components/tado/translations/fr.json | 19 ++++++++++ .../components/timer/translations/hi.json | 9 +++++ .../totalconnect/translations/fr.json | 7 ++++ .../components/vacuum/translations/hi.json | 14 ++++++++ .../components/vera/translations/fr.json | 7 +++- .../components/vera/translations/hi.json | 9 +++++ .../components/weather/translations/hi.json | 21 +++++++++++ .../components/zwave/translations/hi.json | 2 ++ 41 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/airvisual/translations/hi.json create mode 100644 homeassistant/components/atag/translations/hi.json create mode 100644 homeassistant/components/braviatv/translations/hi.json create mode 100644 homeassistant/components/doorbird/translations/pl.json create mode 100644 homeassistant/components/fritzbox/translations/hi.json create mode 100644 homeassistant/components/ipp/translations/pl.json create mode 100644 homeassistant/components/panasonic_viera/translations/hi.json create mode 100644 homeassistant/components/person/translations/hi.json create mode 100644 homeassistant/components/synology_dsm/translations/hi.json create mode 100644 homeassistant/components/synology_dsm/translations/pl.json create mode 100644 homeassistant/components/tado/translations/fr.json create mode 100644 homeassistant/components/timer/translations/hi.json create mode 100644 homeassistant/components/totalconnect/translations/fr.json create mode 100644 homeassistant/components/vacuum/translations/hi.json create mode 100644 homeassistant/components/vera/translations/hi.json create mode 100644 homeassistant/components/weather/translations/hi.json diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index f53113a6b6a..d2013b0f17d 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -7,6 +7,13 @@ "invalid_api_key": "Cl\u00e9 API invalide" }, "step": { + "geography": { + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude" + } + }, "user": { "data": { "api_key": "Cl\u00e9 API", diff --git a/homeassistant/components/airvisual/translations/hi.json b/homeassistant/components/airvisual/translations/hi.json new file mode 100644 index 00000000000..ff9c8ebe206 --- /dev/null +++ b/homeassistant/components/airvisual/translations/hi.json @@ -0,0 +1,34 @@ +{ + "config": { + "error": { + "general_error": "\u0915\u094b\u0908 \u0905\u091c\u094d\u091e\u093e\u0924 \u0924\u094d\u0930\u0941\u091f\u093f \u0925\u0940\u0964", + "unable_to_connect": "\u0928\u094b\u0921 / \u092a\u094d\u0930\u094b \u0907\u0915\u093e\u0908 \u0938\u0947 \u0915\u0928\u0947\u0915\u094d\u091f \u0915\u0930\u0928\u0947 \u092e\u0947\u0902 \u0905\u0938\u092e\u0930\u094d\u0925\u0964" + }, + "step": { + "geography": { + "data": { + "api_key": "\u090f\u092a\u0940\u0906\u0908 \u0915\u0941\u0902\u091c\u0940", + "latitude": "\u0905\u0915\u094d\u0937\u093e\u0902\u0936", + "longitude": "\u0926\u0947\u0936\u093e\u0928\u094d\u0924\u0930" + }, + "description": "\u092d\u094c\u0917\u094b\u0932\u093f\u0915 \u0938\u094d\u0925\u093f\u0924\u093f \u0915\u0940 \u0928\u093f\u0917\u0930\u093e\u0928\u0940 \u0915\u0947 \u0932\u093f\u090f \u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0915\u094d\u0932\u093e\u0909\u0921 \u090f\u092a\u0940\u0906\u0908 \u0915\u093e \u0909\u092a\u092f\u094b\u0917 \u0915\u0930\u0947\u0902\u0964", + "title": "\u092d\u0942\u0917\u094b\u0932 \u0915\u0949\u0928\u094d\u092b\u093c\u093f\u0917\u0930 \u0915\u0930\u0947\u0902" + }, + "node_pro": { + "data": { + "ip_address": "\u0907\u0915\u093e\u0908 \u0915\u0947 \u0906\u0908\u092a\u0940 \u092a\u0924\u0947/\u0939\u094b\u0938\u094d\u091f\u0928\u093e\u092e", + "password": "\u0907\u0915\u093e\u0908 \u092a\u093e\u0938\u0935\u0930\u094d\u0921" + }, + "description": "\u090f\u0915 \u0935\u094d\u092f\u0915\u094d\u0924\u093f\u0917\u0924 \u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0907\u0915\u093e\u0908 \u0915\u0940 \u0928\u093f\u0917\u0930\u093e\u0928\u0940 \u0915\u0930\u0947\u0902\u0964 \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u092f\u0942\u0928\u093f\u091f \u0915\u0947 \u092f\u0942\u0906\u0908 \u0938\u0947 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u093f\u092f\u093e \u091c\u093e \u0938\u0915\u0924\u093e \u0939\u0948\u0964", + "title": "\u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0928\u094b\u0921 \u092a\u094d\u0930\u094b" + }, + "user": { + "data": { + "cloud_api": "\u092d\u094c\u0917\u094b\u0932\u093f\u0915 \u0938\u094d\u0925\u093f\u0924\u093f", + "node_pro": "\u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0928\u094b\u0921 \u092a\u094d\u0930\u094b", + "type": "\u090f\u0915\u0940\u0915\u0930\u0923 \u092a\u094d\u0930\u0915\u093e\u0930" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/fr.json b/homeassistant/components/atag/translations/fr.json index 26b88fe4d20..ace565408f6 100644 --- a/homeassistant/components/atag/translations/fr.json +++ b/homeassistant/components/atag/translations/fr.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "H\u00f4te", "port": "Port (10000)" }, "title": "Se connecter \u00e0 l'appareil" diff --git a/homeassistant/components/atag/translations/hi.json b/homeassistant/components/atag/translations/hi.json new file mode 100644 index 00000000000..e2b57f18e79 --- /dev/null +++ b/homeassistant/components/atag/translations/hi.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "connection_error": "\u0915\u0928\u0947\u0915\u094d\u091f \u0915\u0930\u0928\u0947 \u092e\u0947\u0902 \u0935\u093f\u092b\u0932, \u0915\u0943\u092a\u092f\u093e \u092a\u0941\u0928\u0903 \u092a\u094d\u0930\u092f\u093e\u0938 \u0915\u0930\u0947\u0902" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "\u092a\u094b\u0930\u094d\u091f (10000)" + }, + "title": "\u0921\u093f\u0935\u093e\u0907\u0938 \u0938\u0947 \u0915\u0928\u0947\u0915\u094d\u091f \u0915\u0930\u0947\u0902" + } + } + }, + "title": "A\u091f\u0948\u0917" +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/hi.json b/homeassistant/components/braviatv/translations/hi.json new file mode 100644 index 00000000000..a7a8a15c204 --- /dev/null +++ b/homeassistant/components/braviatv/translations/hi.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "authorize": { + "description": "\u0938\u094b\u0928\u0940 \u092c\u094d\u0930\u093e\u0935\u093f\u092f\u093e \u091f\u0940\u0935\u0940 \u092a\u0930 \u0926\u093f\u0916\u093e\u092f\u093e \u0917\u092f\u093e \u092a\u093f\u0928 \u0915\u094b\u0921 \u0921\u093e\u0932\u0947\u0902\u0964 \n\n \u092f\u0926\u093f \u092a\u093f\u0928 \u0915\u094b\u0921 \u0928\u0939\u0940\u0902 \u0926\u093f\u0916\u093e\u092f\u093e \u0917\u092f\u093e \u0939\u0948, \u0924\u094b \u0906\u092a\u0915\u094b \u0905\u092a\u0928\u0947 \u091f\u0940\u0935\u0940 \u092a\u0930 \u0939\u094b\u092e \u0905\u0938\u093f\u0938\u094d\u091f\u0947\u0902\u091f \u0915\u094b \u0905\u092a\u0902\u091c\u0940\u0915\u0943\u0924 \u0915\u0930\u0928\u093e \u0939\u094b\u0917\u093e, \u0907\u0938\u0915\u0947 \u0932\u093f\u090f \u091c\u093e\u090f\u0902: \u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938 - > \u0928\u0947\u091f\u0935\u0930\u094d\u0915 - > \u0926\u0942\u0930\u0938\u094d\u0925 \u0921\u093f\u0935\u093e\u0907\u0938 \u0938\u0947\u091f\u093f\u0902\u0917\u094d\u0938 - > \u0905\u092a\u0902\u091c\u0940\u0915\u0943\u0924 \u0930\u093f\u092e\u094b\u091f \u0921\u093f\u0935\u093e\u0907\u0938\u0964" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/calendar/translations/hi.json b/homeassistant/components/calendar/translations/hi.json index 654cbbd0bb0..5f1bd39058c 100644 --- a/homeassistant/components/calendar/translations/hi.json +++ b/homeassistant/components/calendar/translations/hi.json @@ -1,7 +1,8 @@ { "state": { "_": { - "off": "\u092c\u0902\u0926" + "off": "\u092c\u0902\u0926", + "on": "\u091a\u093e\u0932\u0942" } } } \ No newline at end of file diff --git a/homeassistant/components/camera/translations/hi.json b/homeassistant/components/camera/translations/hi.json index 911c0011db0..376072b9759 100644 --- a/homeassistant/components/camera/translations/hi.json +++ b/homeassistant/components/camera/translations/hi.json @@ -1,7 +1,8 @@ { "state": { "_": { - "recording": "\u0930\u093f\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917" + "recording": "\u0930\u093f\u0915\u0949\u0930\u094d\u0921\u093f\u0902\u0917", + "streaming": "\u0938\u094d\u091f\u094d\u0930\u0940\u092e\u093f\u0902\u0917" } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index f82a7f2e90a..b7d77ad7b1e 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -29,6 +29,12 @@ "host": "H\u00f4te", "port": "Port" } + }, + "manual_input": { + "data": { + "host": "H\u00f4te", + "port": "Port" + } } } }, diff --git a/homeassistant/components/doorbird/translations/fr.json b/homeassistant/components/doorbird/translations/fr.json index 95a7cfb3086..c208e035d5a 100644 --- a/homeassistant/components/doorbird/translations/fr.json +++ b/homeassistant/components/doorbird/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ce DoorBird est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Ce DoorBird est d\u00e9j\u00e0 configur\u00e9", + "not_doorbird_device": "Cet appareil n'est pas un DoorBird" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json new file mode 100644 index 00000000000..f113b12783d --- /dev/null +++ b/homeassistant/components/doorbird/translations/pl.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "BoorBird jest ju\u017c skonfigurowany." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "name": "Nazwa urz\u0105dzenia", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105czenie z DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Lista wydarze\u0144 oddzielona przecinkami" + }, + "description": "Dodaj nazwy wydarze\u0144 oddzielonych przecinkami, kt\u00f3re chcesz \u015bledzi\u0107. Po wprowadzeniu ich tutaj u\u017cyj aplikacji DoorBird, aby przypisa\u0107 je do okre\u015blonych zdarze\u0144. Zapoznaj si\u0119 z dokumentacj\u0105 na stronie https://www.home-assistant.io/integrations/doorbird/#events.\nPrzyk\u0142ad: nacisnienie_przycisku, ruch" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/fr.json b/homeassistant/components/elkm1/translations/fr.json index 6dd87e84718..27eac521430 100644 --- a/homeassistant/components/elkm1/translations/fr.json +++ b/homeassistant/components/elkm1/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "address_already_configured": "Un ElkM1 avec cette adresse est d\u00e9j\u00e0 configur\u00e9", + "already_configured": "Un ElkM1 avec ce pr\u00e9fixe est d\u00e9j\u00e0 configur\u00e9" + }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", "invalid_auth": "Authentification non valide", diff --git a/homeassistant/components/flume/translations/fr.json b/homeassistant/components/flume/translations/fr.json index b44e5fd8945..a1641a24fc7 100644 --- a/homeassistant/components/flume/translations/fr.json +++ b/homeassistant/components/flume/translations/fr.json @@ -10,6 +10,10 @@ }, "step": { "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, "description": "Pour acc\u00e9der \u00e0 l'API personnel Flume, vous devez demander un \"Client ID\" et un \"Client Secret\" \u00e0 l'adresse https://portal.flumetech.com/settings#token", "title": "Se connecter \u00e0 votre compte Flume" } diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index 8c62d2806c3..5072d62374b 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -8,10 +8,12 @@ "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "description": "Voulez-vous configurer {name} ?" }, "user": { "data": { + "host": "H\u00f4te ou adresse IP", "password": "Mot de passe", "username": "Nom d'utilisateur" } diff --git a/homeassistant/components/fritzbox/translations/hi.json b/homeassistant/components/fritzbox/translations/hi.json new file mode 100644 index 00000000000..f95cac200e9 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/hi.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "not_found": "\u0915\u094b\u0908 \u0938\u092e\u0930\u094d\u0925\u093f\u0924 AVM FRITZ! \u092c\u0949\u0915\u094d\u0938 \u0928\u0947\u091f\u0935\u0930\u094d\u0915 \u092a\u0930 \u0928\u0939\u0940\u0902 \u092e\u093f\u0932\u093e\u0964" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/group/translations/hi.json b/homeassistant/components/group/translations/hi.json index bf3b5329d2d..e4b98d10301 100644 --- a/homeassistant/components/group/translations/hi.json +++ b/homeassistant/components/group/translations/hi.json @@ -1,10 +1,15 @@ { "state": { "_": { + "closed": "\u092c\u0902\u0926", "home": "\u0918\u0930", + "locked": "\u0924\u093e\u0932\u093e \u092c\u0902\u0926 \u0939\u0948", "off": "\u092c\u0902\u0926", + "ok": "\u0920\u0940\u0915", "on": "\u091a\u093e\u0932\u0942", - "problem": "\u0938\u092e\u0938\u094d\u092f\u093e" + "open": "\u0916\u0941\u0932\u093e", + "problem": "\u0938\u092e\u0938\u094d\u092f\u093e", + "unlocked": "\u0924\u093e\u0932\u093e \u0916\u0941\u0932\u093e \u0939\u0948" } }, "title": "\u0938\u092e\u0942\u0939" diff --git a/homeassistant/components/hue/translations/fr.json b/homeassistant/components/hue/translations/fr.json index 45d2a28d35c..b92ccd177e1 100644 --- a/homeassistant/components/hue/translations/fr.json +++ b/homeassistant/components/hue/translations/fr.json @@ -29,8 +29,21 @@ }, "device_automation": { "trigger_subtype": { + "button_1": "Premier bouton", + "button_2": "Deuxi\u00e8me bouton", + "button_3": "Troisi\u00e8me bouton", + "button_4": "Quatri\u00e8me bouton", + "dim_down": "Assombrir", + "dim_up": "\u00c9claircir", "double_buttons_1_3": "Premier et troisi\u00e8me boutons", - "double_buttons_2_4": "Deuxi\u00e8me et quatri\u00e8me boutons" + "double_buttons_2_4": "Deuxi\u00e8me et quatri\u00e8me boutons", + "turn_off": "\u00c9teindre", + "turn_on": "Allumer" + }, + "trigger_type": { + "remote_button_long_release": "Bouton \" {subtype} \" rel\u00e2ch\u00e9 apr\u00e8s un appui long", + "remote_button_short_press": "bouton \"{subtype}\" est press\u00e9", + "remote_button_short_release": "Bouton \" {subtype} \" est rel\u00e2ch\u00e9" } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json new file mode 100644 index 00000000000..0de012d7163 --- /dev/null +++ b/homeassistant/components/ipp/translations/pl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "parse_error": "Nie mo\u017cna przeanalizowa\u0107 odpowiedzi z drukarki." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/de.json b/homeassistant/components/islamic_prayer_times/translations/de.json index c9c7107d9cf..85020d2bf51 100644 --- a/homeassistant/components/islamic_prayer_times/translations/de.json +++ b/homeassistant/components/islamic_prayer_times/translations/de.json @@ -3,5 +3,6 @@ "abort": { "one_instance_allowed": "Es ist nur eine einzige Instanz erforderlich." } - } + }, + "title": "Islamische Gebetszeiten" } \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/hi.json b/homeassistant/components/media_player/translations/hi.json index cdf339d389f..2ed12ab1756 100644 --- a/homeassistant/components/media_player/translations/hi.json +++ b/homeassistant/components/media_player/translations/hi.json @@ -1,8 +1,12 @@ { "state": { "_": { + "idle": "\u0928\u093f\u0937\u094d\u0915\u094d\u0930\u093f\u092f", "off": "\u092c\u0902\u0926", - "on": "\u091a\u093e\u0932\u0942" + "on": "\u091a\u093e\u0932\u0942", + "paused": "\u0935\u093f\u0930\u093e\u092e", + "playing": "\u092c\u091c \u0930\u0939\u093e \u0939\u0948", + "standby": "\u0924\u0948\u092f\u093e\u0930" } }, "title": "\u092e\u0940\u0921\u093f\u092f\u093e \u092a\u094d\u0932\u0947\u092f\u0930" diff --git a/homeassistant/components/nut/translations/de.json b/homeassistant/components/nut/translations/de.json index 5cddc5b0828..793ab5bfa7c 100644 --- a/homeassistant/components/nut/translations/de.json +++ b/homeassistant/components/nut/translations/de.json @@ -18,7 +18,8 @@ "data": { "alias": "Alias", "resources": "Ressourcen" - } + }, + "title": "W\u00e4hle die zu \u00fcberwachende USV" }, "user": { "data": { diff --git a/homeassistant/components/nut/translations/fr.json b/homeassistant/components/nut/translations/fr.json index b83083fd6ed..02927228c3f 100644 --- a/homeassistant/components/nut/translations/fr.json +++ b/homeassistant/components/nut/translations/fr.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", + "unknown": "Erreur inattendue" + }, "step": { "resources": { "data": { @@ -13,6 +20,23 @@ "resources": "Ressources" }, "title": "Choisir l'UPS \u00e0 surveiller" + }, + "user": { + "data": { + "host": "H\u00f4te", + "password": "Mot de passe", + "port": "Port", + "username": "Nom d'utilisateur" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "Ressources" + } } } } diff --git a/homeassistant/components/nws/translations/fr.json b/homeassistant/components/nws/translations/fr.json index bb0eabe7754..94bb529aa62 100644 --- a/homeassistant/components/nws/translations/fr.json +++ b/homeassistant/components/nws/translations/fr.json @@ -6,6 +6,14 @@ "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/fr.json b/homeassistant/components/panasonic_viera/translations/fr.json index 5cf966fc9e6..dc9bfc3c920 100644 --- a/homeassistant/components/panasonic_viera/translations/fr.json +++ b/homeassistant/components/panasonic_viera/translations/fr.json @@ -1,6 +1,14 @@ { "config": { + "abort": { + "unknown": "Une erreur inconnue est survenue. Veuillez consulter les journaux pour obtenir plus de d\u00e9tails." + }, "step": { + "pairing": { + "data": { + "pin": "PIN" + } + }, "user": { "data": { "host": "Adresse IP", @@ -9,5 +17,6 @@ "title": "Configurer votre t\u00e9l\u00e9viseur" } } - } + }, + "title": "Panasonic Viera" } \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/hi.json b/homeassistant/components/panasonic_viera/translations/hi.json new file mode 100644 index 00000000000..30293524362 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/hi.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "pairing": { + "description": "\u0905\u092a\u0928\u0947 \u091f\u0940\u0935\u0940 \u092a\u0930 \u092a\u094d\u0930\u0926\u0930\u094d\u0936\u093f\u0924 \u092a\u093f\u0928 \u0926\u0930\u094d\u091c \u0915\u0930\u0947\u0902" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/person/translations/hi.json b/homeassistant/components/person/translations/hi.json new file mode 100644 index 00000000000..4cd5a4897af --- /dev/null +++ b/homeassistant/components/person/translations/hi.json @@ -0,0 +1,8 @@ +{ + "state": { + "_": { + "home": "\u0918\u0930", + "not_home": "\u0926\u0942\u0930" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 0151782bf1b..0224ce76fcb 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,4 +1,19 @@ { + "config": { + "error": { + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "certificate": "Certificat", + "delay": "D\u00e9lai", + "host": "Nom d'h\u00f4te ou adresse IP", + "password": "Mot de passe" + } + } + } + }, "options": { "step": { "init": { diff --git a/homeassistant/components/smartthings/translations/fr.json b/homeassistant/components/smartthings/translations/fr.json index 8e0acaaaa0f..1fbd4abada4 100644 --- a/homeassistant/components/smartthings/translations/fr.json +++ b/homeassistant/components/smartthings/translations/fr.json @@ -8,6 +8,16 @@ "webhook_error": "SmartThings n'a pas pu valider le point de terminaison configur\u00e9 en \u00ab\u00a0base_url\u00a0\u00bb. Veuillez consulter les exigences du composant." }, "step": { + "pat": { + "data": { + "access_token": "Jeton d'acc\u00e8s" + } + }, + "select_location": { + "data": { + "location_id": "Emplacement" + } + }, "user": { "description": "Veuillez entrer un [jeton d'acc\u00e8s personnel SmartThings] ( {token_url} ) cr\u00e9\u00e9 selon les [instructions] ( {component_url} ).", "title": "Entrer un jeton d'acc\u00e8s personnel" diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index f3ec177fb65..f593ac26182 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -4,6 +4,7 @@ "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat" }, "error": { + "connection": "Error de connexi\u00f3: comprova l'amfitri\u00f3, la contrasenya i l'SSL", "login": "Error d\u2019inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya", "missing_data": "Falten dades: torna-ho a provar m\u00e9s tard o prova una altra configuraci\u00f3 diferent", "otp_failed": "L'autenticaci\u00f3 en dos passos ha fallat, torna-ho a provar amb un nou codi", diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index a0ca9a36273..625bec2c5ae 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -4,6 +4,7 @@ "already_configured": "Host bereits konfiguriert" }, "error": { + "connection": "Verbindungsfehler: Bitte \u00fcberpr\u00fcfe Host, Port und SSL", "login": "Login-Fehler: Bitte \u00fcberpr\u00fcfen Sie Ihren Benutzernamen & Passwort", "missing_data": "Fehlende Daten: Bitte versuchen Sie es sp\u00e4ter noch einmal oder eine andere Konfiguration", "otp_failed": "Die zweistufige Authentifizierung ist fehlgeschlagen. Versuchen Sie es erneut mit einem neuen Code", diff --git a/homeassistant/components/synology_dsm/translations/hi.json b/homeassistant/components/synology_dsm/translations/hi.json new file mode 100644 index 00000000000..de48b719ea7 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/hi.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "connection": "\u0915\u0928\u0947\u0915\u094d\u0936\u0928 \u0924\u094d\u0930\u0941\u091f\u093f: \u0915\u0943\u092a\u092f\u093e \u0905\u092a\u0928\u0947 \u0939\u094b\u0938\u094d\u091f, \u092a\u094b\u0930\u094d\u091f \u0914\u0930 \u090f\u0938\u090f\u0938\u090f\u0932 \u0915\u0940 \u091c\u093e\u0902\u091a \u0915\u0930\u0947\u0902", + "unknown": "\u0905\u091c\u094d\u091e\u093e\u0924 \u0924\u094d\u0930\u0941\u091f\u093f: \u0905\u0927\u093f\u0915 \u0935\u093f\u0935\u0930\u0923 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f \u0932\u0949\u0917 \u0915\u0940 \u091c\u093e\u0902\u091a \u0915\u0930\u0947\u0902" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json new file mode 100644 index 00000000000..d7045b9d1f6 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Host jest ju\u017c skonfigurowany." + }, + "error": { + "login": "B\u0142\u0105d logowania: sprawd\u017a nazw\u0119 u\u017cytkownika i has\u0142o" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "link": { + "data": { + "api_version": "Wersja DSM", + "password": "Has\u0142o", + "port": "Port (opcjonalnie)", + "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "api_version": "Wersja DSM", + "host": "Host", + "password": "Has\u0142o", + "port": "Port (opcjonalnie)", + "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Synology DSM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index c09999b8924..d136f171397 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -4,6 +4,7 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { + "connection": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0445\u043e\u0441\u0442, \u043f\u043e\u0440\u0442 \u0438 SSL.", "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "missing_data": "\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435: \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e.", "otp_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \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, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u0441 \u043d\u043e\u0432\u044b\u043c \u043f\u0430\u0440\u043e\u043b\u0435\u043c.", diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index 1558008cd3b..4e2173324c3 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "connection": "\u9023\u7dda\u932f\u8aa4\uff1a\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u3001\u901a\u8a0a\u57e0\u8207 SSL", "login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u5bc6\u78bc", "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", diff --git a/homeassistant/components/tado/translations/fr.json b/homeassistant/components/tado/translations/fr.json new file mode 100644 index 00000000000..3fa024bb95e --- /dev/null +++ b/homeassistant/components/tado/translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", + "invalid_auth": "Authentification non valide" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/timer/translations/hi.json b/homeassistant/components/timer/translations/hi.json new file mode 100644 index 00000000000..9504ef064ed --- /dev/null +++ b/homeassistant/components/timer/translations/hi.json @@ -0,0 +1,9 @@ +{ + "state": { + "_": { + "active": "\u0938\u0915\u094d\u0930\u093f\u092f", + "idle": "\u0928\u093f\u0937\u094d\u0915\u094d\u0930\u093f\u092f", + "paused": "\u0935\u093f\u0930\u093e\u092e" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json new file mode 100644 index 00000000000..d2c211a7c7c --- /dev/null +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vacuum/translations/hi.json b/homeassistant/components/vacuum/translations/hi.json new file mode 100644 index 00000000000..e8196f3981d --- /dev/null +++ b/homeassistant/components/vacuum/translations/hi.json @@ -0,0 +1,14 @@ +{ + "state": { + "_": { + "cleaning": "\u0938\u092b\u093e\u0908", + "docked": "\u0921\u0949\u0915\u094d\u0921", + "error": "\u0924\u094d\u0930\u0941\u091f\u093f", + "idle": "\u0928\u093f\u0937\u094d\u0915\u094d\u0930\u093f\u092f", + "off": "\u092c\u0902\u0926", + "on": "\u091a\u093e\u0932\u0942", + "paused": "\u0935\u093f\u0930\u093e\u092e", + "returning": "\u0917\u094b\u0926\u0940 \u092e\u0947\u0902 \u0932\u094c\u091f\u0915\u0930" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vera/translations/fr.json b/homeassistant/components/vera/translations/fr.json index 24c7829a14c..4d1b5a7eb6d 100644 --- a/homeassistant/components/vera/translations/fr.json +++ b/homeassistant/components/vera/translations/fr.json @@ -1,3 +1,8 @@ { - "title": "Vera" + "config": { + "abort": { + "already_configured": "Un contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9.", + "cannot_connect": "Impossible de se connecter au contr\u00f4leur avec l'url {base_url}" + } + } } \ No newline at end of file diff --git a/homeassistant/components/vera/translations/hi.json b/homeassistant/components/vera/translations/hi.json new file mode 100644 index 00000000000..0a9092c288b --- /dev/null +++ b/homeassistant/components/vera/translations/hi.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "init": { + "description": "\u0935\u0948\u0915\u0932\u094d\u092a\u093f\u0915 \u092e\u093e\u092a\u0926\u0902\u0921\u094b\u0902 \u0915\u0947 \u0935\u093f\u0935\u0930\u0923 \u0915\u0947 \u0932\u093f\u090f \u0935\u0947\u0930\u093e \u092a\u094d\u0930\u0932\u0947\u0916\u0928 \u0926\u0947\u0916\u0947\u0902: https://www.home-assistant.io/integrations/vera/\u0964 \u0928\u094b\u091f: \u092f\u0939\u093e\u0902 \u0915\u093f\u0938\u0940 \u092d\u0940 \u092c\u0926\u0932\u093e\u0935 \u0915\u0947 \u0932\u093f\u090f \u0939\u094b\u092e \u0905\u0938\u093f\u0938\u094d\u091f\u0947\u0902\u091f \u0938\u0930\u094d\u0935\u0930 \u0915\u094b \u0930\u093f\u0938\u094d\u091f\u093e\u0930\u094d\u091f \u0915\u0930\u0928\u093e \u0939\u094b\u0917\u093e\u0964 \u092e\u0942\u0932\u094d\u092f\u094b\u0902 \u0915\u094b \u0938\u094d\u092a\u0937\u094d\u091f \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093f\u090f, \u090f\u0915 \u0938\u094d\u0925\u093e\u0928 \u092a\u094d\u0930\u0926\u093e\u0928 \u0915\u0930\u0947\u0902\u0964" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/weather/translations/hi.json b/homeassistant/components/weather/translations/hi.json new file mode 100644 index 00000000000..a4fb25b59cf --- /dev/null +++ b/homeassistant/components/weather/translations/hi.json @@ -0,0 +1,21 @@ +{ + "state": { + "_": { + "clear-night": "\u092c\u093f\u0928\u093e \u092c\u093e\u0926\u0932 \u0935\u093e\u0932\u0940 \u0930\u093e\u0924", + "cloudy": "\u092c\u093e\u0926\u0932", + "exceptional": "\u0905\u0938\u093e\u0927\u093e\u0930\u0923", + "fog": "\u0915\u094b\u0939\u0930\u093e", + "hail": "\u0913\u0932\u0947", + "lightning": "\u092c\u093f\u091c\u0932\u0940", + "lightning-rainy": "\u092c\u093f\u091c\u0932\u0940, \u092c\u0930\u0938\u093e\u0924", + "partlycloudy": "\u0906\u0902\u0936\u093f\u0915 \u0930\u0942\u092a \u0938\u0947 \u092c\u093e\u0926\u0932 \u091b\u093e\u090f\u0902\u0917\u0947", + "pouring": "\u092c\u0930\u0938\u093e\u0924\u0940", + "rainy": "\u092c\u0930\u0938\u093e\u0924\u0940", + "snowy": "\u092c\u0930\u094d\u092b\u0940\u0932\u093e", + "snowy-rainy": "\u092c\u0930\u094d\u092b\u0940\u0932\u0940, \u092c\u0930\u0938\u093e\u0924", + "sunny": "\u0927\u0942\u092a", + "windy": "\u0924\u0942\u092b\u093e\u0928\u0940", + "windy-variant": "\u0924\u0942\u092b\u093e\u0928\u0940" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/hi.json b/homeassistant/components/zwave/translations/hi.json index b39e5c1669d..99e98c4aa9f 100644 --- a/homeassistant/components/zwave/translations/hi.json +++ b/homeassistant/components/zwave/translations/hi.json @@ -1,6 +1,8 @@ { "state": { "_": { + "dead": "\u092e\u0943\u0924", + "initializing": "\u0906\u0930\u0902\u092d", "ready": "\u0924\u0948\u092f\u093e\u0930", "sleeping": "\u0938\u094b\u092f\u093e \u0939\u0941\u0906" }, From 4b998ea6afa5b085fe1ef94bacdac4d723a5189b Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Mon, 27 Apr 2020 02:14:53 +0200 Subject: [PATCH 085/511] Improve error handling for Powerwall (#34580) * Improve error handling modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/config_flow.py modified: homeassistant/components/powerwall/const.py modified: homeassistant/components/powerwall/strings.json modified: homeassistant/components/powerwall/translations/en.json * Change exception name modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/config_flow.py * Add test * Rename PowerwallError to POWERWALL_ERROR * Modify handling of APIChangedErrors modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/const.py --- .../components/powerwall/__init__.py | 44 ++++++++++++++++--- .../components/powerwall/config_flow.py | 14 +++++- homeassistant/components/powerwall/const.py | 13 +----- .../components/powerwall/strings.json | 1 + .../components/powerwall/translations/en.json | 3 +- .../components/powerwall/test_config_flow.py | 22 +++++++++- 6 files changed, 76 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index a76393e350a..336c3ac0bff 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging import requests -from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -13,10 +13,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( DOMAIN, + POWERWALL_API_CHANGED, POWERWALL_API_CHARGE, POWERWALL_API_DEVICE_TYPE, POWERWALL_API_GRID_STATUS, @@ -64,7 +65,7 @@ async def _migrate_old_unique_ids(hass, entry_id, powerwall_data): @callback def _async_migrator(entity_entry: entity_registry.RegistryEntry): parts = entity_entry.unique_id.split("_") - # Check if the unique_id starts with the serial_numbers of the powerwakks + # Check if the unique_id starts with the serial_numbers of the powerwalls if parts[0 : len(serial_numbers)] != serial_numbers: # The old unique_id ended with the nomianal_system_engery_kWh so we can use that # to find the old base unique_id and extract the device_suffix. @@ -87,6 +88,17 @@ async def _migrate_old_unique_ids(hass, entry_id, powerwall_data): await entity_registry.async_migrate_entries(hass, entry_id, _async_migrator) +async def _async_handle_api_changed_error(hass: HomeAssistant, error: APIChangedError): + # The error might include some important information about what exactly changed. + _LOGGER.error(str(error)) + hass.components.persistent_notification.async_create( + "It seems like your powerwall uses an unsupported version. " + "Please update the software of your powerwall or if it is" + "already the newest consider reporting this issue.\nSee logs for more information", + title="Unknown powerwall software version", + ) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Tesla Powerwall from a config entry.""" @@ -97,16 +109,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) + await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall) - except (PowerwallUnreachableError, APIError, ConnectionError): + except PowerwallUnreachableError: http_session.close() raise ConfigEntryNotReady + except APIChangedError as err: + http_session.close() + await _async_handle_api_changed_error(hass, err) + return False await _migrate_old_unique_ids(hass, entry_id, powerwall_data) async def async_update_data(): """Fetch data from API endpoint.""" - return await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) + # Check if we had an error before + _LOGGER.info("Checking if update failed") + if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: + _LOGGER.info("Updating data") + try: + return await hass.async_add_executor_job( + _fetch_powerwall_data, power_wall + ) + except PowerwallUnreachableError: + raise UpdateFailed("Unable to fetch data from powerwall") + except APIChangedError as err: + await _async_handle_api_changed_error(hass, err) + hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED] = True + # Returns the cached data. This data can also be None + return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data + else: + return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data coordinator = DataUpdateCoordinator( hass, @@ -122,6 +155,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): POWERWALL_OBJECT: power_wall, POWERWALL_COORDINATOR: coordinator, POWERWALL_HTTP_SESSION: http_session, + POWERWALL_API_CHANGED: False, } ) diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index ca0e2143454..8c313b79024 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Tesla Powerwall integration.""" import logging -from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -25,8 +25,12 @@ async def validate_input(hass: core.HomeAssistant, data): try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) site_info = await hass.async_add_executor_job(power_wall.get_site_info) - except (PowerwallUnreachableError, APIError, ConnectionError): + except PowerwallUnreachableError: raise CannotConnect + except APIChangedError as err: + # Only log the exception without the traceback + _LOGGER.error(str(err)) + raise WrongVersion # Return info that you want to store in the config entry. return {"title": site_info.site_name} @@ -46,6 +50,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" + except WrongVersion: + errors["base"] = "wrong_version" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -69,3 +75,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" + + +class WrongVersion(exceptions.HomeAssistantError): + """Error to indicate the powerwall uses a software version we cannot interact with.""" diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index e2daf1e3760..5f0e9ae3b35 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -4,6 +4,7 @@ DOMAIN = "powerwall" POWERWALL_OBJECT = "powerwall" POWERWALL_COORDINATOR = "coordinator" +POWERWALL_API_CHANGED = "api_changed" UPDATE_INTERVAL = 30 @@ -16,14 +17,6 @@ ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage" ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW" ATTR_IS_ACTIVE = "is_active" -SITE_INFO_UTILITY = "utility" -SITE_INFO_GRID_CODE = "grid_code" -SITE_INFO_NOMINAL_SYSTEM_POWER_KW = "nominal_system_power_kW" -SITE_INFO_NOMINAL_SYSTEM_ENERGY_KWH = "nominal_system_energy_kWh" -SITE_INFO_REGION = "region" - -DEVICE_TYPE_DEVICE_TYPE = "device_type" - STATUS_VERSION = "version" POWERWALL_SITE_NAME = "site_name" @@ -39,10 +32,6 @@ POWERWALL_API_SERIAL_NUMBERS = "serial_numbers" POWERWALL_HTTP_SESSION = "http_session" -POWERWALL_GRID_ONLINE = "SystemGridConnected" -POWERWALL_CONNECTED_KEY = "connected_to_tesla" -POWERWALL_RUNNING_KEY = "running" - POWERWALL_BATTERY_METER = "battery" MODEL = "PowerWall 2" diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index bb5ed671435..ce7e6a1965f 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -8,6 +8,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", + "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", "unknown": "Unexpected error" }, "abort": { "already_configured": "The powerwall is already configured" } diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 378abf486ad..8b45c665d85 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "unknown": "Unexpected error", + "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved." }, "step": { "user": { diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index a6752d838f3..097346c5ac7 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Powerwall config flow.""" from asynctest import patch -from tesla_powerwall import PowerwallUnreachableError +from tesla_powerwall import APIChangedError, PowerwallUnreachableError from homeassistant import config_entries, setup from homeassistant.components.powerwall.const import DOMAIN @@ -86,3 +86,23 @@ async def test_form_cannot_connect(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_wrong_version(hass): + """Test we can handle wrong version error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerwall = _mock_powerwall_side_effect(site_info=APIChangedError(object, {})) + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_IP_ADDRESS: "1.2.3.4"}, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "wrong_version"} From a643d6cd3e2684422a1bb37db2d1107c383267d7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 27 Apr 2020 07:54:39 +0200 Subject: [PATCH 086/511] Upgrade pytest to 5.4.1 (#34739) --- requirements_test.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index a8528c35729..b3525b05c08 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,6 +5,7 @@ -r requirements_test_pre_commit.txt asynctest==0.13.0 codecov==2.0.22 +coverage==5.1 mock-open==1.4.0 mypy==0.770 pre-commit==2.3.0 @@ -15,6 +16,6 @@ pytest-aiohttp==0.3.0 pytest-cov==2.8.1 pytest-sugar==0.9.3 pytest-timeout==1.3.4 -pytest==5.3.5 +pytest==5.4.1 requests_mock==1.7.0 responses==0.10.6 From cc14dfa31cca60276c9c5a689b9521d60c701c94 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 26 Apr 2020 23:35:04 -0700 Subject: [PATCH 087/511] Allow ignoring discovery config flow helper (#34740) --- homeassistant/helpers/config_entry_flow.py | 9 ++--- tests/helpers/test_config_entry_flow.py | 38 +++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 323c6907411..81881d943cd 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -33,6 +33,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): if self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") + await self.async_set_unique_id(self._domain, raise_on_progress=False) + return await self.async_step_confirm() async def async_step_confirm(self, user_input=None): @@ -40,10 +42,7 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): if user_input is None: return self.async_show_form(step_id="confirm") - if ( # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context - and self.context.get("source") != config_entries.SOURCE_DISCOVERY - ): + if self.source == config_entries.SOURCE_USER: # Get current discovered entries. in_progress = self._async_in_progress() @@ -67,6 +66,8 @@ class DiscoveryFlowHandler(config_entries.ConfigFlow): if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="single_instance_allowed") + await self.async_set_unique_id(self._domain) + return await self.async_step_confirm() async_step_zeroconf = async_step_discovery diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 3126c2c0c8d..868ff082ec3 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -44,6 +44,7 @@ async def test_single_entry_allowed(hass, discovery_flow_conf): """Test only a single entry is allowed.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass + flow.context = {} MockConfigEntry(domain="test").add_to_hass(hass) result = await flow.async_step_user() @@ -67,6 +68,7 @@ async def test_user_has_confirmation(hass, discovery_flow_conf): """Test user requires no confirmation to setup.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass + flow.context = {} discovery_flow_conf["discovered"] = True result = await flow.async_step_user() @@ -93,7 +95,7 @@ async def test_discovery_confirmation(hass, discovery_flow_conf, source): """Test we ask for confirmation via discovery.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass - flow.context = {} + flow.context = {"source": source} result = await getattr(flow, f"async_step_{source}")({}) @@ -150,6 +152,7 @@ async def test_import_no_confirmation(hass, discovery_flow_conf): """Test import requires no confirmation to set up.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass + flow.context = {} discovery_flow_conf["discovered"] = True result = await flow.async_step_import(None) @@ -160,6 +163,7 @@ async def test_import_single_instance(hass, discovery_flow_conf): """Test import doesn't create second instance.""" flow = config_entries.HANDLERS["test"]() flow.hass = hass + flow.context = {} discovery_flow_conf["discovered"] = True MockConfigEntry(domain="test").add_to_hass(hass) @@ -167,6 +171,38 @@ async def test_import_single_instance(hass, discovery_flow_conf): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT +async def test_ignored_discoveries(hass, discovery_flow_conf): + """Test we can ignore discovered entries.""" + mock_entity_platform(hass, "config_flow.test", None) + + result = await hass.config_entries.flow.async_init( + "test", context={"source": config_entries.SOURCE_DISCOVERY}, data={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + + flow = next( + ( + flw + for flw in hass.config_entries.flow.async_progress() + if flw["flow_id"] == result["flow_id"] + ), + None, + ) + + # Ignore it. + await hass.config_entries.flow.async_init( + flow["handler"], + context={"source": config_entries.SOURCE_IGNORE}, + data={"unique_id": flow["context"]["unique_id"]}, + ) + + # Second discovery should be aborted + result = await hass.config_entries.flow.async_init( + "test", context={"source": config_entries.SOURCE_DISCOVERY}, data={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + async def test_webhook_single_entry_allowed(hass, webhook_flow_conf): """Test only a single entry is allowed.""" flow = config_entries.HANDLERS["test_single"]() From 2cae17deb76eb003634d373a33340d57591a4ee1 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 27 Apr 2020 09:14:53 +0200 Subject: [PATCH 088/511] Fix dockerfile --- Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 40f281b95eb..fefad4ccbed 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,7 +1,7 @@ FROM python:3.8 RUN \ - && apt-get update && apt-get install -y --no-install-recommends \ + apt-get update && apt-get install -y --no-install-recommends \ software-properties-common \ && add-apt-repository ppa:jonathonf/ffmpeg-4 \ && apt-get update && apt-get install -y --no-install-recommends \ From bec87815589e14d860e20283a1a1e1a59ffa0499 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 27 Apr 2020 09:19:38 +0200 Subject: [PATCH 089/511] cleanup --- Dockerfile.dev | 3 --- 1 file changed, 3 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index fefad4ccbed..b692e9104fe 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,9 +2,6 @@ FROM python:3.8 RUN \ apt-get update && apt-get install -y --no-install-recommends \ - software-properties-common \ - && add-apt-repository ppa:jonathonf/ffmpeg-4 \ - && apt-get update && apt-get install -y --no-install-recommends \ libudev-dev \ libavformat-dev \ libavcodec-dev \ From e4590539e1775e4ce9e988df5059373c567349a2 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 27 Apr 2020 11:36:24 +0200 Subject: [PATCH 090/511] Make ps4 config flow tests robust (#34749) --- tests/components/ps4/test_config_flow.py | 351 +++++++++++++++-------- 1 file changed, 232 insertions(+), 119 deletions(-) diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 06f10da4f2b..2bad2d1e281 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -1,6 +1,7 @@ """Define tests for the PlayStation 4 config flow.""" from asynctest import patch from pyps4_2ndscreen.errors import CredentialTimeout +import pytest from homeassistant import data_entry_flow from homeassistant.components import ps4 @@ -73,22 +74,40 @@ MOCK_LOCATION = location.LocationInfo( ) +@pytest.fixture(name="location_info", autouse=True) +def location_info_fixture(): + """Mock location info.""" + with patch( + "homeassistant.components.ps4.config_flow.location.async_detect_location_info", + return_value=MOCK_LOCATION, + ): + yield + + +@pytest.fixture(name="ps4_setup", autouse=True) +def ps4_setup_fixture(): + """Patch ps4 setup entry.""" + with patch( + "homeassistant.components.ps4.async_setup_entry", return_value=True, + ): + yield + + async def test_full_flow_implementation(hass): """Test registering an implementation and flow works.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.location = MOCK_LOCATION - manager = hass.config_entries - # User Step Started, results in Step Creds with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): - result = await flow.async_step_user() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "creds" # Step Creds results with form in Step Mode. with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): - result = await flow.async_step_creds({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mode" @@ -96,7 +115,9 @@ async def test_full_flow_implementation(hass): with patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_mode(MOCK_AUTO) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" @@ -104,44 +125,30 @@ async def test_full_flow_implementation(hass): with patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)), patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_link(MOCK_CONFIG) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_TOKEN] == MOCK_CREDS assert result["data"]["devices"] == [MOCK_DEVICE] assert result["title"] == MOCK_TITLE - await hass.async_block_till_done() - - # Add entry using result data. - mock_data = { - CONF_TOKEN: result["data"][CONF_TOKEN], - "devices": result["data"]["devices"], - } - entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data) - entry.add_to_manager(manager) - - # Check if entry exists. - assert len(manager.async_entries()) == 1 - # Check if there is a device config in entry. - assert len(entry.data["devices"]) == 1 - async def test_multiple_flow_implementation(hass): """Test multiple device flows.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.location = MOCK_LOCATION - manager = hass.config_entries - # User Step Started, results in Step Creds with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): - result = await flow.async_step_user() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "creds" # Step Creds results with form in Step Mode. with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): - result = await flow.async_step_creds({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mode" @@ -150,7 +157,9 @@ async def test_multiple_flow_implementation(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], ): - result = await flow.async_step_mode(MOCK_AUTO) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" @@ -159,26 +168,20 @@ async def test_multiple_flow_implementation(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], ): - result = await flow.async_step_link(MOCK_CONFIG) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_TOKEN] == MOCK_CREDS assert result["data"]["devices"] == [MOCK_DEVICE] assert result["title"] == MOCK_TITLE - await hass.async_block_till_done() - - # Add entry using result data. - mock_data = { - CONF_TOKEN: result["data"][CONF_TOKEN], - "devices": result["data"]["devices"], - } - entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data) - entry.add_to_manager(manager) - # Check if entry exists. - assert len(manager.async_entries()) == 1 + entries = hass.config_entries.async_entries() + assert len(entries) == 1 # Check if there is a device config in entry. - assert len(entry.data["devices"]) == 1 + entry_1 = entries[0] + assert len(entry_1.data["devices"]) == 1 # Test additional flow. @@ -187,13 +190,17 @@ async def test_multiple_flow_implementation(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], ): - result = await flow.async_step_user() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "creds" # Step Creds results with form in Step Mode. with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): - result = await flow.async_step_creds({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mode" @@ -202,7 +209,9 @@ async def test_multiple_flow_implementation(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], ): - result = await flow.async_step_mode(MOCK_AUTO) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" @@ -211,44 +220,40 @@ async def test_multiple_flow_implementation(hass): "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], ), patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)): - result = await flow.async_step_link(MOCK_CONFIG_ADDITIONAL) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG_ADDITIONAL + ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_TOKEN] == MOCK_CREDS assert len(result["data"]["devices"]) == 1 assert result["title"] == MOCK_TITLE - await hass.async_block_till_done() - - mock_data = { - CONF_TOKEN: result["data"][CONF_TOKEN], - "devices": result["data"]["devices"], - } - - # Update config entries with result data - entry = MockConfigEntry(domain=ps4.DOMAIN, data=mock_data) - entry.add_to_manager(manager) - manager.async_update_entry(entry) - # Check if there are 2 entries. - assert len(manager.async_entries()) == 2 - # Check if there is device config in entry. - assert len(entry.data["devices"]) == 1 + entries = hass.config_entries.async_entries() + assert len(entries) == 2 + # Check if there is device config in the last entry. + entry_2 = entries[-1] + assert len(entry_2.data["devices"]) == 1 + + # Check that entry 1 is different from entry 2. + assert entry_1 is not entry_2 async def test_port_bind_abort(hass): """Test that flow aborted when cannot bind to ports 987, 997.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - with patch("pyps4_2ndscreen.Helper.port_bind", return_value=MOCK_UDP_PORT): reason = "port_987_bind_error" - result = await flow.async_step_user(user_input=None) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == reason with patch("pyps4_2ndscreen.Helper.port_bind", return_value=MOCK_TCP_PORT): reason = "port_997_bind_error" - result = await flow.async_step_user(user_input=None) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == reason @@ -256,48 +261,69 @@ async def test_port_bind_abort(hass): async def test_duplicate_abort(hass): """Test that Flow aborts when found devices already configured.""" MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA).add_to_hass(hass) - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.creds = MOCK_CREDS + + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" + + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" with patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_link(user_input=None) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "devices_configured" async def test_additional_device(hass): """Test that Flow can configure another device.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.creds = MOCK_CREDS - manager = hass.config_entries - # Mock existing entry. entry = MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA) - entry.add_to_manager(manager) - # Check that only 1 entry exists - assert len(manager.async_entries()) == 1 + entry.add_to_hass(hass) + + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" + + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" with patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}, {"host-ip": MOCK_HOST_ADDITIONAL}], - ), patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)): - result = await flow.async_step_link(MOCK_CONFIG_ADDITIONAL) + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) + + with patch("pyps4_2ndscreen.Helper.link", return_value=(True, True)): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG_ADDITIONAL + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"][CONF_TOKEN] == MOCK_CREDS assert len(result["data"]["devices"]) == 1 assert result["title"] == MOCK_TITLE - # Add New Entry - entry = MockConfigEntry(domain=ps4.DOMAIN, data=MOCK_DATA) - entry.add_to_manager(manager) - - # Check that there are 2 entries - assert len(manager.async_entries()) == 2 - async def test_0_pin(hass): """Test Pin with leading '0' is passed correctly.""" @@ -337,63 +363,121 @@ async def test_0_pin(hass): async def test_no_devices_found_abort(hass): """Test that failure to find devices aborts flow.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" + + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" with patch("pyps4_2ndscreen.Helper.has_devices", return_value=[]): - result = await flow.async_step_link() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_devices_found" async def test_manual_mode(hass): """Test host specified in manual mode is passed to Step Link.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.location = MOCK_LOCATION + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" + + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" # Step Mode with User Input: manual, results in Step Link. with patch( - "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": flow.m_device}] + "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_mode(MOCK_MANUAL) - assert flow.m_device == MOCK_HOST + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_MANUAL + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" async def test_credential_abort(hass): """Test that failure to get credentials aborts flow.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" with patch("pyps4_2ndscreen.Helper.get_creds", return_value=None): - result = await flow.async_step_creds({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "credential_error" async def test_credential_timeout(hass): """Test that Credential Timeout shows error.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" with patch("pyps4_2ndscreen.Helper.get_creds", side_effect=CredentialTimeout): - result = await flow.async_step_creds({}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" assert result["errors"] == {"base": "credential_timeout"} async def test_wrong_pin_error(hass): """Test that incorrect pin throws an error.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.location = MOCK_LOCATION + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" - with patch("pyps4_2ndscreen.Helper.link", return_value=(True, False)), patch( + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" + + with patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_link(MOCK_CONFIG) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) + + with patch("pyps4_2ndscreen.Helper.link", return_value=(True, False)): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" assert result["errors"] == {"base": "login_failed"} @@ -401,14 +485,31 @@ async def test_wrong_pin_error(hass): async def test_device_connection_error(hass): """Test that device not connected or on throws an error.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass - flow.location = MOCK_LOCATION + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" - with patch("pyps4_2ndscreen.Helper.link", return_value=(False, True)), patch( + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" + + with patch( "pyps4_2ndscreen.Helper.has_devices", return_value=[{"host-ip": MOCK_HOST}] ): - result = await flow.async_step_link(MOCK_CONFIG) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_AUTO + ) + + with patch("pyps4_2ndscreen.Helper.link", return_value=(False, True)): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=MOCK_CONFIG + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "link" assert result["errors"] == {"base": "not_ready"} @@ -416,12 +517,24 @@ async def test_device_connection_error(hass): async def test_manual_mode_no_ip_error(hass): """Test no IP specified in manual mode throws an error.""" - flow = ps4.PlayStation4FlowHandler() - flow.hass = hass + with patch("pyps4_2ndscreen.Helper.port_bind", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "creds" - mock_input = {"Config Mode": "Manual Entry"} + with patch("pyps4_2ndscreen.Helper.get_creds", return_value=MOCK_CREDS): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "mode" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"Config Mode": "Manual Entry"} + ) - result = await flow.async_step_mode(mock_input) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "mode" assert result["errors"] == {CONF_IP_ADDRESS: "no_ipaddress"} From be57d456532897fa48d29a8ccb28b1a16e9875eb Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 27 Apr 2020 12:09:31 +0200 Subject: [PATCH 091/511] Add hadolint to CI (#34758) * Add hadolint to CI * Fix lint & name * Update azure-pipelines-ci.yml Co-Authored-By: Franck Nijhof Co-authored-by: Franck Nijhof --- .hadolint.yaml | 5 +++++ Dockerfile.dev | 3 +-- azure-pipelines-ci.yml | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .hadolint.yaml diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 00000000000..06de09b5460 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,5 @@ +ignored: + - DL3006 + - DL3008 + - DL3013 + - DL3018 diff --git a/Dockerfile.dev b/Dockerfile.dev index b692e9104fe..be8e2223390 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,8 +18,7 @@ WORKDIR /usr/src # Setup hass-release RUN git clone --depth 1 https://github.com/home-assistant/hass-release \ - && cd hass-release \ - && pip3 install -e . + && pip3 install -e hass-release/ WORKDIR /workspaces diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index d620cd14594..49b032ed2e8 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -26,6 +26,8 @@ resources: variables: - name: PythonMain value: "37" + - name: versionHadolint + value: "v1.17.6" stages: - stage: "Overview" @@ -121,6 +123,22 @@ stages: . venv/bin/activate pre-commit run black --all-files --show-diff-on-failure displayName: "Check Black formatting" + - job: "Docker" + pool: + vmImage: "ubuntu-latest" + steps: + - script: sudo docker pull hadolint/hadolint:$(versionHadolint) + displayName: "Install Hadolint" + - script: | + set -e + for dockerfile in Dockerfile Dockerfile.dev + do + echo "Linting: $dockerfile" + docker run --rm -i \ + -v "$(pwd)/.hadolint.yaml:/.hadolint.yaml:ro" \ + hadolint/hadolint:$(versionHadolint) < "$dockerfile" + done + displayName: "Run Hadolint" - stage: "Tests" dependsOn: From 5e249aac5fcfc67d255c9b376ff40dd3db855d7d Mon Sep 17 00:00:00 2001 From: rajlaud <50647620+rajlaud@users.noreply.github.com> Date: Mon, 27 Apr 2020 05:15:00 -0500 Subject: [PATCH 092/511] Refactor squeezebox (#34731) Co-Authored-By: Martin Hjelmare --- CODEOWNERS | 1 + homeassistant/components/squeezebox/const.py | 7 + .../components/squeezebox/manifest.json | 3 +- .../components/squeezebox/media_player.py | 258 ++++-------------- requirements_all.txt | 3 + 5 files changed, 71 insertions(+), 201 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 815f1b6b85a..f60da8494d7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -363,6 +363,7 @@ homeassistant/components/speedtestdotnet/* @rohankapoorcom homeassistant/components/spider/* @peternijssen homeassistant/components/spotify/* @frenck homeassistant/components/sql/* @dgomes +homeassistant/components/squeezebox/* @rajlaud homeassistant/components/starline/* @anonym-tsk homeassistant/components/statistics/* @fabaff homeassistant/components/stiebel_eltron/* @fucm diff --git a/homeassistant/components/squeezebox/const.py b/homeassistant/components/squeezebox/const.py index 1e8fd6f3a2a..e7e52fe2d80 100644 --- a/homeassistant/components/squeezebox/const.py +++ b/homeassistant/components/squeezebox/const.py @@ -1,3 +1,10 @@ """Constants for the Squeezebox component.""" +from homeassistant.const import STATE_IDLE, STATE_PAUSED, STATE_PLAYING + DOMAIN = "squeezebox" SERVICE_CALL_METHOD = "call_method" +SQUEEZEBOX_MODE = { + "pause": STATE_PAUSED, + "play": STATE_PLAYING, + "stop": STATE_IDLE, +} diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json index bbd32e9eefe..13c200fc46f 100644 --- a/homeassistant/components/squeezebox/manifest.json +++ b/homeassistant/components/squeezebox/manifest.json @@ -2,5 +2,6 @@ "domain": "squeezebox", "name": "Logitech Squeezebox", "documentation": "https://www.home-assistant.io/integrations/squeezebox", - "codeowners": [] + "codeowners": ["@rajlaud"], + "requirements": ["pysqueezebox==0.1.2"] } diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index f40374d9486..8b27defd5e1 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -1,12 +1,9 @@ """Support for interfacing to the Logitech SqueezeBox API.""" import asyncio -import json import logging import socket -import urllib.parse -import aiohttp -import async_timeout +from pysqueezebox import Server import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity @@ -33,18 +30,14 @@ from homeassistant.const import ( CONF_PASSWORD, CONF_PORT, CONF_USERNAME, - HTTP_OK, - STATE_IDLE, STATE_OFF, - STATE_PAUSED, - STATE_PLAYING, ) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow -from .const import DOMAIN, SERVICE_CALL_METHOD +from .const import DOMAIN, SERVICE_CALL_METHOD, SQUEEZEBOX_MODE _LOGGER = logging.getLogger(__name__) @@ -126,7 +119,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= # Get IP of host, to prevent duplication of same host (different DNS names) try: - ipaddr = socket.gethostbyname(host) + ipaddr = await hass.async_add_executor_job(socket.gethostbyname, host) except OSError as error: _LOGGER.error("Could not communicate with %s:%d: %s", host, port, error) raise PlatformNotReady from error @@ -135,16 +128,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return _LOGGER.debug("Creating LMS object for %s", ipaddr) - lms = LogitechMediaServer(hass, host, port, username, password) - - players = await lms.create_players() - if players is None: - raise PlatformNotReady - + lms = Server(async_get_clientsession(hass), host, port, username, password) known_servers.add(ipaddr) - hass.data[DATA_SQUEEZEBOX].extend(players) - async_add_entities(players) + players = await lms.async_get_players() + if players is None: + raise PlatformNotReady + media_players = [] + for player in players: + media_players.append(SqueezeBoxDevice(player)) + + hass.data[DATA_SQUEEZEBOX].extend(media_players) + async_add_entities(media_players) async def async_service_handler(service): """Map services to methods on MediaPlayerEntity.""" @@ -182,133 +177,41 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= return True -class LogitechMediaServer: - """Representation of a Logitech media server.""" - - def __init__(self, hass, host, port, username, password): - """Initialize the Logitech device.""" - self.hass = hass - self.host = host - self.port = port - self._username = username - self._password = password - - async def create_players(self): - """Create a list of devices connected to LMS.""" - result = [] - data = await self.async_query("players", "status") - if data is False: - return None - for players in data.get("players_loop", []): - player = SqueezeBoxDevice(self, players["playerid"], players["name"]) - await player.async_update() - result.append(player) - return result - - async def async_query(self, *command, player=""): - """Abstract out the JSON-RPC connection.""" - auth = ( - None - if self._username is None - else aiohttp.BasicAuth(self._username, self._password) - ) - url = f"http://{self.host}:{self.port}/jsonrpc.js" - data = json.dumps( - {"id": "1", "method": "slim.request", "params": [player, command]} - ) - - _LOGGER.debug("URL: %s Data: %s", url, data) - - try: - websession = async_get_clientsession(self.hass) - with async_timeout.timeout(TIMEOUT): - response = await websession.post(url, data=data, auth=auth) - - if response.status != HTTP_OK: - _LOGGER.error( - "Query failed, response code: %s Full message: %s", - response.status, - response, - ) - return False - - data = await response.json() - - except (asyncio.TimeoutError, aiohttp.ClientError) as error: - _LOGGER.error("Failed communicating with LMS: %s", type(error)) - return False - - try: - return data["result"] - except AttributeError: - _LOGGER.error("Received invalid response: %s", data) - return False - - class SqueezeBoxDevice(MediaPlayerEntity): - """Representation of a SqueezeBox device.""" + """ + Representation of a SqueezeBox device. - def __init__(self, lms, player_id, name): + Wraps a pysqueezebox.Player() object. + """ + + def __init__(self, player): """Initialize the SqueezeBox device.""" - super().__init__() - self._lms = lms - self._id = player_id - self._status = {} - self._name = name + self._player = player self._last_update = None - _LOGGER.debug("Creating SqueezeBox object: %s, %s", name, player_id) @property def name(self): """Return the name of the device.""" - return self._name + return self._player.name @property def unique_id(self): """Return a unique ID.""" - return self._id + return self._player.player_id @property def state(self): """Return the state of the device.""" - if "power" in self._status and self._status["power"] == 0: + if self._player.power is not None and not self._player.power: return STATE_OFF - if "mode" in self._status: - if self._status["mode"] == "pause": - return STATE_PAUSED - if self._status["mode"] == "play": - return STATE_PLAYING - if self._status["mode"] == "stop": - return STATE_IDLE + if self._player.mode: + return SQUEEZEBOX_MODE.get(self._player.mode) return None - async def async_query(self, *parameters): - """Send a command to the LMS.""" - return await self._lms.async_query(*parameters, player=self._id) - async def async_update(self): - """Retrieve the current state of the player.""" - tags = "adKl" - response = await self.async_query("status", "-", "1", f"tags:{tags}") - - if response is False: - return - + """Update the Player() object.""" last_media_position = self.media_position - - self._status = {} - - try: - self._status.update(response["playlist_loop"][0]) - except KeyError: - pass - try: - self._status.update(response["remoteMeta"]) - except KeyError: - pass - - self._status.update(response) - + await self._player.async_update() if self.media_position != last_media_position: _LOGGER.debug( "Media position updated for %s: %s", self, self.media_position @@ -318,20 +221,18 @@ class SqueezeBoxDevice(MediaPlayerEntity): @property def volume_level(self): """Volume level of the media player (0..1).""" - if "mixer volume" in self._status: - return int(float(self._status["mixer volume"])) / 100.0 + if self._player.volume: + return int(float(self._player.volume)) / 100.0 @property def is_volume_muted(self): """Return true if volume is muted.""" - if "mixer volume" in self._status: - return str(self._status["mixer volume"]).startswith("-") + return self._player.muting @property def media_content_id(self): """Content ID of current playing media.""" - if "current_title" in self._status: - return self._status["current_title"] + return self._player.url @property def media_content_type(self): @@ -341,14 +242,12 @@ class SqueezeBoxDevice(MediaPlayerEntity): @property def media_duration(self): """Duration of current playing media in seconds.""" - if "duration" in self._status: - return int(float(self._status["duration"])) + return self._player.duration @property def media_position(self): - """Duration of current playing media in seconds.""" - if "time" in self._status: - return int(float(self._status["time"])) + """Position of current playing media in seconds.""" + return self._player.time @property def media_position_updated_at(self): @@ -358,60 +257,27 @@ class SqueezeBoxDevice(MediaPlayerEntity): @property def media_image_url(self): """Image url of current playing media.""" - if "artwork_url" in self._status: - media_url = self._status["artwork_url"] - elif "id" in self._status: - media_url = ("/music/{track_id}/cover.jpg").format( - track_id=self._status["id"] - ) - else: - media_url = ("/music/current/cover.jpg?player={player}").format( - player=self._id - ) - - # pylint: disable=protected-access - if self._lms._username: - base_url = "http://{username}:{password}@{server}:{port}/".format( - username=self._lms._username, - password=self._lms._password, - server=self._lms.host, - port=self._lms.port, - ) - else: - base_url = "http://{server}:{port}/".format( - server=self._lms.host, port=self._lms.port - ) - - url = urllib.parse.urljoin(base_url, media_url) - - return url + return self._player.image_url @property def media_title(self): """Title of current playing media.""" - if "title" in self._status: - return self._status["title"] - - if "current_title" in self._status: - return self._status["current_title"] + return self._player.title @property def media_artist(self): """Artist of current playing media.""" - if "artist" in self._status: - return self._status["artist"] + return self._player.artist @property def media_album_name(self): """Album of current playing media.""" - if "album" in self._status: - return self._status["album"] + return self._player.album @property def shuffle(self): """Boolean if shuffle is enabled.""" - if "playlist_shuffle" in self._status: - return self._status["playlist_shuffle"] == 1 + return self._player.shuffle @property def supported_features(self): @@ -420,53 +286,52 @@ class SqueezeBoxDevice(MediaPlayerEntity): async def async_turn_off(self): """Turn off media player.""" - await self.async_query("power", "0") + await self._player.async_set_power(False) async def async_volume_up(self): """Volume up media player.""" - await self.async_query("mixer", "volume", "+5") + await self._player.async_set_volume("+5") async def async_volume_down(self): """Volume down media player.""" - await self.async_query("mixer", "volume", "-5") + await self._player.async_set_volume("-5") async def async_set_volume_level(self, volume): """Set volume level, range 0..1.""" volume_percent = str(int(volume * 100)) - await self.async_query("mixer", "volume", volume_percent) + await self._player.async_set_volume(volume_percent) async def async_mute_volume(self, mute): """Mute (true) or unmute (false) media player.""" - mute_numeric = "1" if mute else "0" - await self.async_query("mixer", "muting", mute_numeric) + await self._player.async_set_muting(mute) async def async_media_play_pause(self): """Send pause command to media player.""" - await self.async_query("pause") + await self._player.async_toggle_pause() async def async_media_play(self): """Send play command to media player.""" - await self.async_query("play") + await self._player.async_play() async def async_media_pause(self): """Send pause command to media player.""" - await self.async_query("pause", "1") + await self._player.async_pause() async def async_media_next_track(self): """Send next track command.""" - await self.async_query("playlist", "index", "+1") + await self._player.async_index("+1") async def async_media_previous_track(self): """Send next track command.""" - await self.async_query("playlist", "index", "-1") + await self._player.async_index("-1") async def async_media_seek(self, position): """Send seek command.""" - await self.async_query("time", position) + await self._player.async_time(position) async def async_turn_on(self): """Turn the media player on.""" - await self.async_query("power", "1") + await self._player.async_set_power(True) async def async_play_media(self, media_type, media_id, **kwargs): """ @@ -474,27 +339,20 @@ class SqueezeBoxDevice(MediaPlayerEntity): If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the current playlist. """ + cmd = "play" if kwargs.get(ATTR_MEDIA_ENQUEUE): - await self._add_uri_to_playlist(media_id) - return + cmd = "add" - await self._play_uri(media_id) - - async def _play_uri(self, media_id): - """Replace the current play list with the uri.""" - await self.async_query("playlist", "play", media_id) - - async def _add_uri_to_playlist(self, media_id): - """Add an item to the existing playlist.""" - await self.async_query("playlist", "add", media_id) + await self._player.async_load_url(media_id, cmd) async def async_set_shuffle(self, shuffle): """Enable/disable shuffle mode.""" - await self.async_query("playlist", "shuffle", int(shuffle)) + shuffle_mode = "song" if shuffle else "none" + await self._player.async_set_shuffle(shuffle_mode) async def async_clear_playlist(self): """Send the media player the command for clear playlist.""" - await self.async_query("playlist", "clear") + await self._player.async_clear_playlist() async def async_call_method(self, command, parameters=None): """ @@ -507,4 +365,4 @@ class SqueezeBoxDevice(MediaPlayerEntity): if parameters: for parameter in parameters: all_params.append(parameter) - await self.async_query(*all_params) + await self._player.async_query(*all_params) diff --git a/requirements_all.txt b/requirements_all.txt index 724f59f874d..10b9251de24 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1580,6 +1580,9 @@ pysonos==0.0.25 # homeassistant.components.spc pyspcwebgw==0.4.0 +# homeassistant.components.squeezebox +pysqueezebox==0.1.2 + # homeassistant.components.stiebel_eltron pystiebeleltron==0.0.1.dev2 From 96ef92659f0e0ea88ab759b00e26d03c9252e8f5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 27 Apr 2020 15:38:14 +0200 Subject: [PATCH 093/511] Updated frontend to 20200427.0 (#34766) --- 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 f75605eaafc..f75f164aca5 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200424.0"], + "requirements": ["home-assistant-frontend==20200427.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a6d72ca222f..2d3ae822273 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.1 -home-assistant-frontend==20200424.0 +home-assistant-frontend==20200427.0 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 10b9251de24..5ac55859bdd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -713,7 +713,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200424.0 +home-assistant-frontend==20200427.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0b1fe306ada..c58311e91d0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -291,7 +291,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200424.0 +home-assistant-frontend==20200427.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From b876f7f11a8c30c22a62679ec894b888e61d00ff Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 27 Apr 2020 10:41:33 -0600 Subject: [PATCH 094/511] Bump simplisafe-python to 9.2.0 (#34750) --- homeassistant/components/simplisafe/__init__.py | 4 +++- homeassistant/components/simplisafe/config_flow.py | 2 +- homeassistant/components/simplisafe/manifest.json | 2 +- homeassistant/components/simplisafe/strings.json | 12 +++++++++--- .../components/simplisafe/translations/en.json | 3 ++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index 63bf1f5b8fa..a726c822cb0 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -223,7 +223,9 @@ async def async_setup_entry(hass, config_entry): websession = aiohttp_client.async_get_clientsession(hass) try: - api = await API.login_via_token(config_entry.data[CONF_TOKEN], websession) + api = await API.login_via_token( + config_entry.data[CONF_TOKEN], session=websession + ) except InvalidCredentialsError: _LOGGER.error("Invalid credentials provided") return False diff --git a/homeassistant/components/simplisafe/config_flow.py b/homeassistant/components/simplisafe/config_flow.py index 031d5496f9d..1225f6de818 100644 --- a/homeassistant/components/simplisafe/config_flow.py +++ b/homeassistant/components/simplisafe/config_flow.py @@ -57,7 +57,7 @@ class SimpliSafeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): try: simplisafe = await API.login_via_credentials( - user_input[CONF_USERNAME], user_input[CONF_PASSWORD], websession + user_input[CONF_USERNAME], user_input[CONF_PASSWORD], session=websession ) except SimplipyError: return await self._show_form(errors={"base": "invalid_credentials"}) diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index 4fdf87ee88f..6b271012c8e 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.1.0"], + "requirements": ["simplisafe-python==9.2.0"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/simplisafe/strings.json b/homeassistant/components/simplisafe/strings.json index 3d9d832c99a..7aceed5ce2e 100644 --- a/homeassistant/components/simplisafe/strings.json +++ b/homeassistant/components/simplisafe/strings.json @@ -2,8 +2,12 @@ "config": { "step": { "user": { - "title": "Fill in your information", - "data": { "username": "Email Address", "password": "Password" } + "title": "Fill in your information.", + "data": { + "username": "Email Address", + "password": "Password", + "code": "Code (used in Home Assistant UI)" + } } }, "error": { @@ -18,7 +22,9 @@ "step": { "init": { "title": "Configure SimpliSafe", - "data": { "code": "Code (used in Home Assistant UI)" } + "data": { + "code": "Code (used in Home Assistant UI)" + } } } } diff --git a/homeassistant/components/simplisafe/translations/en.json b/homeassistant/components/simplisafe/translations/en.json index 1cbaeffe958..3d568a0875e 100644 --- a/homeassistant/components/simplisafe/translations/en.json +++ b/homeassistant/components/simplisafe/translations/en.json @@ -10,10 +10,11 @@ "step": { "user": { "data": { + "code": "Code (used in Home Assistant UI)", "password": "Password", "username": "Email Address" }, - "title": "Fill in your information" + "title": "Fill in your information." } } }, diff --git a/requirements_all.txt b/requirements_all.txt index 5ac55859bdd..1b1924b2b28 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1888,7 +1888,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==9.1.0 +simplisafe-python==9.2.0 # homeassistant.components.sisyphus sisyphus-control==2.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c58311e91d0..d16c18ee149 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -722,7 +722,7 @@ sentry-sdk==0.13.5 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==9.1.0 +simplisafe-python==9.2.0 # homeassistant.components.sleepiq sleepyq==0.7 From ed925f9ef5319e900a4b5d8c826ac6587a19be62 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 27 Apr 2020 10:05:02 -0700 Subject: [PATCH 095/511] Set up config entries in parallel (#34755) --- homeassistant/setup.py | 8 ++++-- tests/test_setup.py | 55 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 82b7e1be039..70321d364b8 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -201,8 +201,12 @@ async def _async_setup_component( await asyncio.sleep(0) await hass.config_entries.flow.async_wait_init_flow_finish(domain) - for entry in hass.config_entries.async_entries(domain): - await entry.async_setup(hass, integration=integration) + await asyncio.gather( + *[ + entry.async_setup(hass, integration=integration) + for entry in hass.config_entries.async_entries(domain) + ] + ) hass.config.components.add(domain) diff --git a/tests/test_setup.py b/tests/test_setup.py index daa9c6e8406..a5e53861ef0 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -1,13 +1,15 @@ """Test component/platform setup.""" # pylint: disable=protected-access +import asyncio import logging import os import threading -from unittest import mock +from asynctest import Mock, patch +import pytest import voluptuous as vol -from homeassistant import setup +from homeassistant import config_entries, setup import homeassistant.config as config_util from homeassistant.const import EVENT_COMPONENT_LOADED, EVENT_HOMEASSISTANT_START from homeassistant.core import callback @@ -19,6 +21,7 @@ from homeassistant.helpers.config_validation import ( import homeassistant.util.dt as dt_util from tests.common import ( + MockConfigEntry, MockModule, MockPlatform, assert_setup_component, @@ -34,6 +37,19 @@ VERSION_PATH = os.path.join(get_test_config_dir(), config_util.VERSION_FILE) _LOGGER = logging.getLogger(__name__) +@pytest.fixture(autouse=True) +def mock_handlers(): + """Mock config flows.""" + + class MockFlowHandler(config_entries.ConfigFlow): + """Define a mock flow handler.""" + + VERSION = 1 + + with patch.dict(config_entries.HANDLERS, {"comp": MockFlowHandler}): + yield + + class TestSetup: """Test the bootstrap utils.""" @@ -239,7 +255,7 @@ class TestSetup: def test_component_not_double_initialized(self): """Test we do not set up a component twice.""" - mock_setup = mock.MagicMock(return_value=True) + mock_setup = Mock(return_value=True) mock_integration(self.hass, MockModule("comp", setup=mock_setup)) @@ -251,7 +267,7 @@ class TestSetup: assert setup.setup_component(self.hass, "comp", {}) assert not mock_setup.called - @mock.patch("homeassistant.util.package.install_package", return_value=False) + @patch("homeassistant.util.package.install_package", return_value=False) def test_component_not_installed_if_requirement_fails(self, mock_install): """Component setup should fail if requirement can't install.""" self.hass.config.skip_pip = False @@ -350,7 +366,7 @@ class TestSetup: {"valid": True}, extra=vol.PREVENT_EXTRA ) - mock_setup = mock.MagicMock(spec_set=True) + mock_setup = Mock(spec_set=True) mock_entity_platform( self.hass, @@ -469,7 +485,7 @@ async def test_component_cannot_depend_config(hass): async def test_component_warn_slow_setup(hass): """Warn we log when a component setup takes a long time.""" mock_integration(hass, MockModule("test_component1")) - with mock.patch.object(hass.loop, "call_later", mock.MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: result = await setup.async_setup_component(hass, "test_component1", {}) assert result assert mock_call.called @@ -488,7 +504,7 @@ async def test_platform_no_warn_slow(hass): mock_integration( hass, MockModule("test_component1", platform_schema=PLATFORM_SCHEMA) ) - with mock.patch.object(hass.loop, "call_later", mock.MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: result = await setup.async_setup_component(hass, "test_component1", {}) assert result assert not mock_call.called @@ -524,7 +540,30 @@ async def test_when_setup_already_loaded(hass): async def test_setup_import_blows_up(hass): """Test that we handle it correctly when importing integration blows up.""" - with mock.patch( + with patch( "homeassistant.loader.Integration.get_component", side_effect=ValueError ): assert not await setup.async_setup_component(hass, "sun", {}) + + +async def test_parallel_entry_setup(hass): + """Test config entries are set up in parallel.""" + MockConfigEntry(domain="comp", data={"value": 1}).add_to_hass(hass) + MockConfigEntry(domain="comp", data={"value": 2}).add_to_hass(hass) + + calls = [] + + async def mock_async_setup_entry(hass, entry): + """Mock setting up an entry.""" + calls.append(entry.data["value"]) + await asyncio.sleep(0) + calls.append(entry.data["value"]) + return True + + mock_integration( + hass, MockModule("comp", async_setup_entry=mock_async_setup_entry,), + ) + mock_entity_platform(hass, "config_flow.comp", None) + await setup.async_setup_component(hass, "comp", {}) + + assert calls == [1, 2, 1, 2] From 360ac7e71f00012b1d455e3da66adf69a0c33540 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Mon, 27 Apr 2020 13:23:16 -0400 Subject: [PATCH 096/511] Add Rachio rain delay switch (#34741) * Add Rachio Rain Delay Switch * Typo * Catch KeyError * Use HA dt module in place of time --- homeassistant/components/rachio/const.py | 2 + homeassistant/components/rachio/switch.py | 74 ++++++++++++++++++++- homeassistant/components/rachio/webhooks.py | 6 ++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 3508e24eb4b..99e26f63835 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -23,6 +23,7 @@ KEY_NAME = "name" KEY_MODEL = "model" KEY_ON = "on" KEY_DURATION = "totalDuration" +KEY_RAIN_DELAY = "rainDelayExpirationDate" KEY_STATUS = "status" KEY_SUBTYPE = "subType" KEY_SUMMARY = "summary" @@ -56,6 +57,7 @@ STATUS_OFFLINE = "OFFLINE" SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update" SIGNAL_RACHIO_CONTROLLER_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_controller" +SIGNAL_RACHIO_RAIN_DELAY_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_rain_delay" SIGNAL_RACHIO_ZONE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_zone" SIGNAL_RACHIO_SCHEDULE_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_schedule" diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index f9cfa6ab122..f1f2cb26687 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -6,6 +6,7 @@ import logging from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util.dt import as_timestamp, now from .const import ( ATTR_ZONE_SHADE, @@ -22,17 +23,21 @@ from .const import ( KEY_IMAGE_URL, KEY_NAME, KEY_ON, + KEY_RAIN_DELAY, KEY_SCHEDULE_ID, KEY_SUBTYPE, KEY_SUMMARY, KEY_ZONE_ID, KEY_ZONE_NUMBER, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) from .entity import RachioDevice from .webhooks import ( + SUBTYPE_RAIN_DELAY_OFF, + SUBTYPE_RAIN_DELAY_ON, SUBTYPE_SCHEDULE_COMPLETED, SUBTYPE_SCHEDULE_STARTED, SUBTYPE_SCHEDULE_STOPPED, @@ -67,6 +72,7 @@ def _create_entities(hass, config_entry): # in order to avoid every zone doing it for controller in person.controllers: entities.append(RachioStandbySwitch(controller)) + entities.append(RachioRainDelay(controller)) zones = controller.list_zones() schedules = controller.list_schedules() flex_schedules = controller.list_flex_schedules() @@ -179,6 +185,72 @@ class RachioStandbySwitch(RachioSwitch): ) +class RachioRainDelay(RachioSwitch): + """Representation of a rain delay status/switch.""" + + def __init__(self, controller): + """Instantiate a new Rachio rain delay switch.""" + super().__init__(controller, poll=True) + self._poll_update(controller.init_data) + + @property + def name(self) -> str: + """Return the name of the switch.""" + return f"{self._controller.name} rain delay" + + @property + def unique_id(self) -> str: + """Return a unique id by combining controller id and purpose.""" + return f"{self._controller.controller_id}-delay" + + @property + def icon(self) -> str: + """Return an icon for rain delay.""" + return "mdi:camera-timer" + + def _poll_update(self, data=None) -> bool: + """Request the state from the API.""" + # API returns either 0 or current UNIX time when rain delay was canceled + # depending if it was done from the app or via the API + if data is None: + data = self._controller.rachio.device.get(self._controller.controller_id)[1] + + try: + return data[KEY_RAIN_DELAY] / 1000 > as_timestamp(now()) + except KeyError: + return False + + @callback + def _async_handle_update(self, *args, **kwargs) -> None: + """Update the state using webhook data.""" + if args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_DELAY_ON: + self._state = True + elif args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_DELAY_OFF: + self._state = False + + self.async_write_ha_state() + + def turn_on(self, **kwargs) -> None: + """Activate a 24 hour rain delay on the controller.""" + self._controller.rachio.device.rainDelay(self._controller.controller_id, 86400) + _LOGGER.debug("Starting rain delay for 24 hours") + + def turn_off(self, **kwargs) -> None: + """Resume controller functionality.""" + self._controller.rachio.device.rainDelay(self._controller.controller_id, 0) + _LOGGER.debug("Canceling rain delay") + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, + self._async_handle_any_update, + ) + ) + + class RachioZone(RachioSwitch): """Representation of one zone of sprinklers connected to the Rachio Iro.""" @@ -320,7 +392,7 @@ class RachioSchedule(RachioSwitch): @property def icon(self) -> str: """Return the icon to display.""" - return "mdi:water" + return "mdi:water" if self.schedule_is_enabled else "mdi:water-off" @property def device_state_attributes(self) -> dict: diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index a5960b8b28b..a3f95d5a5f3 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -15,6 +15,7 @@ from .const import ( KEY_EXTERNAL_ID, KEY_TYPE, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) @@ -30,6 +31,9 @@ SUBTYPE_SLEEP_MODE_OFF = "SLEEP_MODE_OFF" SUBTYPE_BROWNOUT_VALVE = "BROWNOUT_VALVE" SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON" SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF" + +# Rain delay values +TYPE_RAIN_DELAY_STATUS = "RAIN_DELAY" SUBTYPE_RAIN_DELAY_ON = "RAIN_DELAY_ON" SUBTYPE_RAIN_DELAY_OFF = "RAIN_DELAY_OFF" @@ -55,6 +59,7 @@ SUBTYPE_ZONE_CYCLING_COMPLETED = "ZONE_CYCLING_COMPLETED" LISTEN_EVENT_TYPES = [ "DEVICE_STATUS_EVENT", "ZONE_STATUS_EVENT", + "RAIN_DELAY_EVENT", "SCHEDULE_STATUS_EVENT", ] WEBHOOK_CONST_ID = "homeassistant.rachio:" @@ -62,6 +67,7 @@ WEBHOOK_PATH = URL_API + DOMAIN SIGNAL_MAP = { TYPE_CONTROLLER_STATUS: SIGNAL_RACHIO_CONTROLLER_UPDATE, + TYPE_RAIN_DELAY_STATUS: SIGNAL_RACHIO_RAIN_DELAY_UPDATE, TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE, TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE, } From d74ece92f98e788d69d6429168a172ab322a04e1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 27 Apr 2020 10:36:56 -0700 Subject: [PATCH 097/511] Validate that discovered config flows set a unique ID (#34751) Co-Authored-By: Franck Nijhof --- script/hassfest/config_flow.py | 61 ++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/script/hassfest/config_flow.py b/script/hassfest/config_flow.py index 6971cc28fc9..c31e77ccdff 100644 --- a/script/hassfest/config_flow.py +++ b/script/hassfest/config_flow.py @@ -2,6 +2,8 @@ import json from typing import Dict +from homeassistant.requirements import DISCOVERY_INTEGRATIONS + from .model import Config, Integration BASE = """ @@ -15,33 +17,50 @@ To update, run python3 -m script.hassfest FLOWS = {} """.strip() +UNIQUE_ID_IGNORE = {"esphome", "fritzbox", "heos", "huawei_lte"} -def validate_integration(integration: Integration): - """Validate we can load config flow without installing requirements.""" - if not (integration.path / "config_flow.py").is_file(): + +def validate_integration(config: Config, integration: Integration): + """Validate config flow of an integration.""" + config_flow_file = integration.path / "config_flow.py" + + if not config_flow_file.is_file(): integration.add_error( "config_flow", "Config flows need to be defined in the file config_flow.py" ) + return - # Currently not require being able to load config flow without - # installing requirements. - # try: - # integration.import_pkg('config_flow') - # except ImportError as err: - # integration.add_error( - # 'config_flow', - # "Unable to import config flow: {}. Config flows should be able " - # "to be imported without installing requirements.".format(err)) - # return + needs_unique_id = integration.domain not in UNIQUE_ID_IGNORE and any( + key in integration.manifest + for keys in DISCOVERY_INTEGRATIONS.values() + for key in keys + ) - # if integration.domain not in config_entries.HANDLERS: - # integration.add_error( - # 'config_flow', - # "Importing the config flow platform did not register a config " - # "flow handler.") + if not needs_unique_id: + return + + config_flow = config_flow_file.read_text() + + has_unique_id = ( + "self.async_set_unique_id" in config_flow + or "config_entry_flow.register_discovery_flow" in config_flow + or "config_entry_oauth2_flow.AbstractOAuth2FlowHandler" in config_flow + ) + + if has_unique_id: + return + + if config.specific_integrations: + notice_method = integration.add_warning + else: + notice_method = integration.add_error + + notice_method( + "config_flow", "Config flows that are discoverable need to set a unique ID" + ) -def generate_and_validate(integrations: Dict[str, Integration]): +def generate_and_validate(integrations: Dict[str, Integration], config: Config): """Validate and generate config flow data.""" domains = [] @@ -56,7 +75,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): if not config_flow: continue - validate_integration(integration) + validate_integration(config, integration) domains.append(domain) @@ -66,7 +85,7 @@ def generate_and_validate(integrations: Dict[str, Integration]): 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.cache["config_flow"] = content = generate_and_validate(integrations, config) if config.specific_integrations: return From b8ebb94bc6763932cda8fc435fe687e36f16ea01 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 27 Apr 2020 11:02:25 -0700 Subject: [PATCH 098/511] Disable upnp SSDP discovery (#34756) --- homeassistant/components/upnp/manifest.json | 10 +--------- homeassistant/generated/ssdp.py | 8 -------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index e3b30cec9a4..2f6e5de5884 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -5,13 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/upnp", "requirements": ["async-upnp-client==0.14.13"], "dependencies": [], - "codeowners": ["@StevenLooman"], - "ssdp": [ - { - "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" - }, - { - "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2" - } - ] + "codeowners": ["@StevenLooman"] } diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index f46ba1611a8..5dbef37d9bf 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -81,14 +81,6 @@ SSDP = { "manufacturer": "Synology" } ], - "upnp": [ - { - "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" - }, - { - "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2" - } - ], "wemo": [ { "manufacturer": "Belkin International Inc." From 8237ccfa03805db7129d06468cb6fc99667216e7 Mon Sep 17 00:00:00 2001 From: escoand Date: Mon, 27 Apr 2020 20:18:32 +0200 Subject: [PATCH 099/511] Add unique_id to fritzbox (#34716) --- .../components/fritzbox/config_flow.py | 20 +++++++++--- tests/components/fritzbox/test_config_flow.py | 32 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index 816855b46a8..b4265aa01fe 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -5,7 +5,11 @@ from pyfritzhome import Fritzhome, LoginError import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.ssdp import ATTR_SSDP_LOCATION, ATTR_UPNP_FRIENDLY_NAME +from homeassistant.components.ssdp import ( + ATTR_SSDP_LOCATION, + ATTR_UPNP_FRIENDLY_NAME, + ATTR_UPNP_UDN, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME # pylint:disable=unused-import @@ -82,10 +86,6 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): for entry in self.hass.config_entries.async_entries(DOMAIN): if entry.data[CONF_HOST] == user_input[CONF_HOST]: - if entry.data != user_input: - self.hass.config_entries.async_update_entry( - entry, data=user_input - ) return self.async_abort(reason="already_configured") self._host = user_input[CONF_HOST] @@ -110,12 +110,22 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): host = urlparse(user_input[ATTR_SSDP_LOCATION]).hostname self.context[CONF_HOST] = host + uuid = user_input.get(ATTR_UPNP_UDN) + if uuid: + if uuid.startswith("uuid:"): + uuid = uuid[5:] + await self.async_set_unique_id(uuid) + self._abort_if_unique_id_configured({CONF_HOST: host}) + for progress in self._async_in_progress(): if progress.get("context", {}).get(CONF_HOST) == host: return self.async_abort(reason="already_in_progress") + # update old and user-configured config entries for entry in self.hass.config_entries.async_entries(DOMAIN): if entry.data[CONF_HOST] == host: + if uuid and not entry.unique_id: + self.hass.config_entries.async_update_entry(entry, unique_id=uuid) return self.async_abort(reason="already_configured") self._host = host diff --git a/tests/components/fritzbox/test_config_flow.py b/tests/components/fritzbox/test_config_flow.py index d6b43dc4b71..c73578de646 100644 --- a/tests/components/fritzbox/test_config_flow.py +++ b/tests/components/fritzbox/test_config_flow.py @@ -6,7 +6,11 @@ from pyfritzhome import LoginError import pytest from homeassistant.components.fritzbox.const import DOMAIN -from homeassistant.components.ssdp import ATTR_SSDP_LOCATION, ATTR_UPNP_FRIENDLY_NAME +from homeassistant.components.ssdp import ( + ATTR_SSDP_LOCATION, + ATTR_UPNP_FRIENDLY_NAME, + ATTR_UPNP_UDN, +) from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.typing import HomeAssistantType @@ -16,6 +20,7 @@ MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] MOCK_SSDP_DATA = { ATTR_SSDP_LOCATION: "https://fake_host:12345/test", ATTR_UPNP_FRIENDLY_NAME: "fake_name", + ATTR_UPNP_UDN: "uuid:only-a-test", } @@ -42,6 +47,7 @@ async def test_user(hass: HomeAssistantType, fritz: Mock): assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_PASSWORD] == "fake_pass" assert result["data"][CONF_USERNAME] == "fake_user" + assert not result["result"].unique_id async def test_user_auth_failed(hass: HomeAssistantType, fritz: Mock): @@ -73,6 +79,7 @@ async def test_user_already_configured(hass: HomeAssistantType, fritz: Mock): DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA ) assert result["type"] == "create_entry" + assert not result["result"].unique_id result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA @@ -91,6 +98,7 @@ async def test_import(hass: HomeAssistantType, fritz: Mock): assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_PASSWORD] == "fake_pass" assert result["data"][CONF_USERNAME] == "fake_user" + assert not result["result"].unique_id async def test_ssdp(hass: HomeAssistantType, fritz: Mock): @@ -110,6 +118,7 @@ async def test_ssdp(hass: HomeAssistantType, fritz: Mock): assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_PASSWORD] == "fake_pass" assert result["data"][CONF_USERNAME] == "fake_user" + assert result["result"].unique_id == "only-a-test" async def test_ssdp_auth_failed(hass: HomeAssistantType, fritz: Mock): @@ -150,7 +159,7 @@ async def test_ssdp_not_successful(hass: HomeAssistantType, fritz: Mock): assert result["reason"] == "not_found" -async def test_ssdp_already_in_progress(hass: HomeAssistantType, fritz: Mock): +async def test_ssdp_already_in_progress_unique_id(hass: HomeAssistantType, fritz: Mock): """Test starting a flow from discovery twice.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "ssdp"}, data=MOCK_SSDP_DATA @@ -165,15 +174,34 @@ async def test_ssdp_already_in_progress(hass: HomeAssistantType, fritz: Mock): assert result["reason"] == "already_in_progress" +async def test_ssdp_already_in_progress_host(hass: HomeAssistantType, fritz: Mock): + """Test starting a flow from discovery twice.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "ssdp"}, data=MOCK_SSDP_DATA + ) + assert result["type"] == "form" + assert result["step_id"] == "confirm" + + MOCK_NO_UNIQUE_ID = MOCK_SSDP_DATA.copy() + del MOCK_NO_UNIQUE_ID[ATTR_UPNP_UDN] + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "ssdp"}, data=MOCK_NO_UNIQUE_ID + ) + assert result["type"] == "abort" + assert result["reason"] == "already_in_progress" + + async def test_ssdp_already_configured(hass: HomeAssistantType, fritz: Mock): """Test starting a flow from discovery when already configured.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "user"}, data=MOCK_USER_DATA ) assert result["type"] == "create_entry" + assert not result["result"].unique_id result2 = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "ssdp"}, data=MOCK_SSDP_DATA ) assert result2["type"] == "abort" assert result2["reason"] == "already_configured" + assert result["result"].unique_id == "only-a-test" From b768c9cc64eae83f149c9a1359041c8d08466dc9 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 27 Apr 2020 21:57:57 +0200 Subject: [PATCH 100/511] Arcam fmj bump library to 0.4.4 (#34687) * Bump arcam-fmj with better connection failed support * Log unexpected exceptions in arcam client * Consider undetected as 2ch to match OSD * Ask for explicit update on start --- homeassistant/components/arcam_fmj/__init__.py | 5 +++++ homeassistant/components/arcam_fmj/manifest.json | 2 +- homeassistant/components/arcam_fmj/media_player.py | 10 ++++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index 59bcd08a641..aa11e66d49c 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -162,3 +162,8 @@ async def _run_client(hass, client, interval): await asyncio.sleep(interval) except asyncio.TimeoutError: continue + except asyncio.CancelledError: + return + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception, aborting arcam client") + return diff --git a/homeassistant/components/arcam_fmj/manifest.json b/homeassistant/components/arcam_fmj/manifest.json index 5508f4b6869..c304d7bf351 100644 --- a/homeassistant/components/arcam_fmj/manifest.json +++ b/homeassistant/components/arcam_fmj/manifest.json @@ -3,6 +3,6 @@ "name": "Arcam FMJ Receivers", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/arcam_fmj", - "requirements": ["arcam-fmj==0.4.3"], + "requirements": ["arcam-fmj==0.4.4"], "codeowners": ["@elupus"] } diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index c9e0c8b8e37..125b3bf96b1 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -57,7 +57,8 @@ async def async_setup_entry( zone_config.get(SERVICE_TURN_ON), ) for zone, zone_config in config[CONF_ZONE].items() - ] + ], + True, ) return True @@ -86,7 +87,12 @@ class ArcamFmj(MediaPlayerEntity): audio_format, _ = self._state.get_incoming_audio_format() return bool( audio_format - in (IncomingAudioFormat.PCM, IncomingAudioFormat.ANALOGUE_DIRECT, None) + in ( + IncomingAudioFormat.PCM, + IncomingAudioFormat.ANALOGUE_DIRECT, + IncomingAudioFormat.UNDETECTED, + None, + ) ) @property diff --git a/requirements_all.txt b/requirements_all.txt index 1b1924b2b28..5f3a3a8977e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -260,7 +260,7 @@ aprslib==0.6.46 aqualogic==1.0 # homeassistant.components.arcam_fmj -arcam-fmj==0.4.3 +arcam-fmj==0.4.4 # homeassistant.components.arris_tg2492lg arris-tg2492lg==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d16c18ee149..cea7c6df1a4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -119,7 +119,7 @@ apprise==0.8.5 aprslib==0.6.46 # homeassistant.components.arcam_fmj -arcam-fmj==0.4.3 +arcam-fmj==0.4.4 # homeassistant.components.dlna_dmr # homeassistant.components.upnp From e13f78dfc5f79c23759a070de76f4aff8388845a Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 28 Apr 2020 00:04:52 +0000 Subject: [PATCH 101/511] [ci skip] Translation update --- .../components/adguard/translations/pl.json | 2 +- .../components/airvisual/translations/lb.json | 4 +-- .../components/airvisual/translations/no.json | 4 +-- .../components/airvisual/translations/pl.json | 24 ++++++++++++-- .../components/atag/translations/pl.json | 20 ++++++++++++ .../components/axis/translations/pl.json | 2 +- .../components/climate/translations/no.json | 7 +++- .../configurator/translations/no.json | 6 ++++ .../coolmaster/translations/pl.json | 2 +- .../components/cover/translations/no.json | 1 + .../components/daikin/translations/pl.json | 2 +- .../components/deconz/translations/pl.json | 17 +++++++++- .../components/demo/translations/lb.json | 2 +- .../components/doorbird/translations/pl.json | 5 ++- .../components/elkm1/translations/pl.json | 27 ++++++++++++++++ .../emulated_roku/translations/lb.json | 2 +- .../emulated_roku/translations/pl.json | 2 +- .../components/esphome/translations/lb.json | 2 +- .../components/esphome/translations/pl.json | 2 +- .../components/flume/translations/lb.json | 1 + .../components/flume/translations/pl.json | 24 ++++++++++++++ .../flunearyou/translations/pl.json | 18 +++++++++++ .../components/freebox/translations/pl.json | 2 +- .../components/fritzbox/translations/pl.json | 32 +++++++++++++++++++ .../components/glances/translations/pl.json | 2 +- .../components/group/translations/lb.json | 2 +- .../components/heos/translations/pl.json | 4 +-- .../homekit_controller/translations/lb.json | 2 +- .../components/hue/translations/pl.json | 23 ++++++++++++- .../components/icloud/translations/pl.json | 2 +- .../components/ipp/translations/pl.json | 27 ++++++++++++++++ .../islamic_prayer_times/translations/pl.json | 23 +++++++++++++ .../components/konnected/translations/pl.json | 1 + .../components/local_ip/translations/pl.json | 3 ++ .../components/lock/translations/lb.json | 2 +- .../components/mailgun/translations/lb.json | 2 +- .../components/mikrotik/translations/pl.json | 2 +- .../minecraft_server/translations/pl.json | 2 +- .../components/monoprice/translations/pl.json | 15 +++++++++ .../components/nut/translations/pl.json | 2 +- .../components/nws/translations/pl.json | 23 +++++++++++++ .../components/owntracks/translations/pl.json | 2 +- .../panasonic_viera/translations/pl.json | 31 ++++++++++++++++++ .../components/powerwall/translations/ca.json | 3 +- .../components/powerwall/translations/es.json | 3 +- .../components/powerwall/translations/lb.json | 3 +- .../components/powerwall/translations/no.json | 3 +- .../components/powerwall/translations/pl.json | 3 +- .../components/powerwall/translations/ru.json | 3 +- .../powerwall/translations/zh-Hant.json | 3 +- .../components/roomba/translations/lb.json | 1 + .../components/roomba/translations/pl.json | 32 +++++++++++++++++++ .../smartthings/translations/lb.json | 8 +++-- .../smartthings/translations/pl.json | 19 +++++++++++ .../components/soma/translations/pl.json | 2 +- .../components/sun/translations/no.json | 6 ++++ .../synology_dsm/translations/es.json | 1 + .../synology_dsm/translations/it.json | 4 +-- .../synology_dsm/translations/lb.json | 1 + .../synology_dsm/translations/no.json | 3 +- .../synology_dsm/translations/pl.json | 14 ++++++-- .../components/tado/translations/lb.json | 1 + .../components/tado/translations/pl.json | 29 +++++++++++++++++ .../tellduslive/translations/pl.json | 2 +- .../totalconnect/translations/pl.json | 19 +++++++++++ .../components/tradfri/translations/pl.json | 2 +- .../transmission/translations/pl.json | 2 +- .../components/unifi/translations/pl.json | 5 +-- .../components/upnp/translations/lb.json | 2 +- .../components/vera/translations/pl.json | 31 ++++++++++++++++++ .../components/vizio/translations/lb.json | 12 +++---- .../components/vizio/translations/pl.json | 8 ++++- .../components/wled/translations/lb.json | 16 +++++----- .../components/zwave/translations/lb.json | 6 ++-- 74 files changed, 555 insertions(+), 72 deletions(-) create mode 100644 homeassistant/components/atag/translations/pl.json create mode 100644 homeassistant/components/elkm1/translations/pl.json create mode 100644 homeassistant/components/flume/translations/pl.json create mode 100644 homeassistant/components/flunearyou/translations/pl.json create mode 100644 homeassistant/components/fritzbox/translations/pl.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/pl.json create mode 100644 homeassistant/components/nws/translations/pl.json create mode 100644 homeassistant/components/panasonic_viera/translations/pl.json create mode 100644 homeassistant/components/roomba/translations/pl.json create mode 100644 homeassistant/components/tado/translations/pl.json create mode 100644 homeassistant/components/totalconnect/translations/pl.json create mode 100644 homeassistant/components/vera/translations/pl.json diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index 7b2886b660e..71264e906e4 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -16,7 +16,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", "ssl": "AdGuard Home u\u017cywa certyfikatu SSL", diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index d5f78eccf0b..52e55242a05 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "D\u00ebs Koordinate si schon registr\u00e9iert." + "already_configured": "D\u00ebs Koordinate oder ode/Pro ID si schon registr\u00e9iert." }, "error": { "general_error": "Onbekannten Feeler", - "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel", + "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel uginn", "unable_to_connect": "Kann sech net mat der Node/Pri verbannen." }, "step": { diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index 77ab1425c87..a528c98e04c 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Disse koordinatene er allerede registrert." + "already_configured": "Disse koordinatene eller Node / Pro ID er allerede registrert." }, "error": { "general_error": "Det oppstod en ukjent feil.", @@ -35,7 +35,7 @@ "node_pro": "AirVisual Node Pro", "type": "Integrasjonstype" }, - "description": "Overv\u00e5k luftkvaliteten p\u00e5 et geografisk sted.", + "description": "Velg hvilken type AirVisual-data du vil overv\u00e5ke.", "title": "Konfigurer AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index cb73ed656f2..8687a2ead03 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -4,14 +4,34 @@ "already_configured": "Ten klucz API jest ju\u017c w u\u017cyciu." }, "error": { - "invalid_api_key": "Nieprawid\u0142owy klucz API" + "general_error": "Nieznany b\u0142\u0105d", + "invalid_api_key": "Nieprawid\u0142owy klucz API", + "unable_to_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z jednostk\u0105 Node/Pro." }, "step": { - "user": { + "geography": { "data": { "api_key": "Klucz API", "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna" + }, + "title": "Konfiguracja Geography" + }, + "node_pro": { + "data": { + "ip_address": "Nazwa hosta lub adres IP jednostki", + "password": "Has\u0142o jednostki" + }, + "description": "Has\u0142o", + "title": "Konfiguracja AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "Klucz API", + "cloud_api": "Lokalizacja geograficzna", + "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "node_pro": "AirVisual Node Pro", "type": "Typ integracji" }, "description": "Monitoruj jako\u015b\u0107 powietrza w okre\u015blonej lokalizacji geograficznej.", diff --git a/homeassistant/components/atag/translations/pl.json b/homeassistant/components/atag/translations/pl.json new file mode 100644 index 00000000000..e931c1fc10f --- /dev/null +++ b/homeassistant/components/atag/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Do Home Assistant mo\u017cna doda\u0107 tylko jedno urz\u0105dzenie Atag" + }, + "error": { + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie." + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port (10000)" + }, + "title": "Po\u0142\u0105cz z urz\u0105dzeniem" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index cc53bafb57d..7473c62f668 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -16,7 +16,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", "username": "Nazwa u\u017cytkownika" diff --git a/homeassistant/components/climate/translations/no.json b/homeassistant/components/climate/translations/no.json index 900022c4709..4e9722bb207 100644 --- a/homeassistant/components/climate/translations/no.json +++ b/homeassistant/components/climate/translations/no.json @@ -16,7 +16,12 @@ }, "state": { "_": { - "heat": "Varme" + "auto": "Auto", + "cool": "Kj\u00f8le", + "dry": "T\u00f8rr", + "fan_only": "Kun vifte", + "heat": "Varme", + "heat_cool": "Varme/kj\u00f8lig" } }, "title": "Klima" diff --git a/homeassistant/components/configurator/translations/no.json b/homeassistant/components/configurator/translations/no.json index 099945f6201..1f923920583 100644 --- a/homeassistant/components/configurator/translations/no.json +++ b/homeassistant/components/configurator/translations/no.json @@ -1,3 +1,9 @@ { + "state": { + "_": { + "configure": "Konfigurer", + "configured": "Konfigurert" + } + }, "title": "Konfigurator" } \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/pl.json b/homeassistant/components/coolmaster/translations/pl.json index 8407a8429b6..9b0e4bc5846 100644 --- a/homeassistant/components/coolmaster/translations/pl.json +++ b/homeassistant/components/coolmaster/translations/pl.json @@ -12,7 +12,7 @@ "fan_only": "Obs\u0142uga trybu \"tylko wentylator\"", "heat": "Obs\u0142uga trybu grzania", "heat_cool": "Obs\u0142uga automatycznego trybu grzanie/ch\u0142odzenie", - "host": "Host", + "host": "Nazwa hosta lub adres IP", "off": "Mo\u017ce by\u0107 wy\u0142\u0105czone" }, "title": "Skonfiguruj szczeg\u00f3\u0142y po\u0142\u0105czenia CoolMasterNet." diff --git a/homeassistant/components/cover/translations/no.json b/homeassistant/components/cover/translations/no.json index 840ede020f4..4d898ec75f8 100644 --- a/homeassistant/components/cover/translations/no.json +++ b/homeassistant/components/cover/translations/no.json @@ -28,6 +28,7 @@ "state": { "_": { "closing": "Lukker", + "opening": "\u00c5pner", "stopped": "Stoppet" } }, diff --git a/homeassistant/components/daikin/translations/pl.json b/homeassistant/components/daikin/translations/pl.json index e4444c3f0c1..2e2f65bc008 100644 --- a/homeassistant/components/daikin/translations/pl.json +++ b/homeassistant/components/daikin/translations/pl.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "host": "Host" + "host": "Nazwa hosta lub adres IP" }, "description": "Wprowad\u017a adres IP Daikin AC.", "title": "Konfiguracja Daikin AC" diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 0b9f336e9cd..a9bff098644 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -26,15 +26,29 @@ }, "manual_confirm": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "port": "Port" } + }, + "manual_input": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port" + }, + "title": "Konfiguracja bramki deCONZ" + }, + "user": { + "data": { + "host": "Wybierz znalezion\u0105 bramk\u0119 deCONZ" + }, + "title": "Wybierz bramk\u0119 deCONZ" } } }, "device_automation": { "trigger_subtype": { "both_buttons": "oba przyciski", + "bottom_buttons": "Dolne przyciski", "button_1": "pierwszy przycisk", "button_2": "drugi przycisk", "button_3": "trzeci przycisk", @@ -51,6 +65,7 @@ "side_4": "strona 4", "side_5": "strona 5", "side_6": "strona 6", + "top_buttons": "G\u00f3rne przyciski", "turn_off": "nast\u0105pi wy\u0142\u0105czenie", "turn_on": "nast\u0105pi w\u0142\u0105czenie" }, diff --git a/homeassistant/components/demo/translations/lb.json b/homeassistant/components/demo/translations/lb.json index 3787d37750c..864a3603f3d 100644 --- a/homeassistant/components/demo/translations/lb.json +++ b/homeassistant/components/demo/translations/lb.json @@ -4,7 +4,7 @@ "init": { "data": { "one": "Een", - "other": "Aner" + "other": "M\u00e9i" } }, "options_1": { diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json index f113b12783d..61511297468 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "BoorBird jest ju\u017c skonfigurowany." + "already_configured": "BoorBird jest ju\u017c skonfigurowany.", + "link_local_address": "Po\u0142\u0105czenie lokalnego adresu nie jest obs\u0142ugiwane", + "not_doorbird_device": "To urz\u0105dzenie nie jest urz\u0105dzeniem DoorBird" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", "invalid_auth": "Niepoprawne uwierzytelnienie.", "unknown": "Niespodziewany b\u0142\u0105d." }, + "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/pl.json b/homeassistant/components/elkm1/translations/pl.json new file mode 100644 index 00000000000..3a445b1008c --- /dev/null +++ b/homeassistant/components/elkm1/translations/pl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "ElkM1 z tym adresem jest ju\u017c skonfigurowany.", + "already_configured": "ElkM1 z tym prefiksem jest ju\u017c skonfigurowany." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "address": "Adres IP, domena lub port szeregowy w przypadku po\u0142\u0105czenia szeregowego.", + "password": "Has\u0142o (tylko bezpieczne).", + "prefix": "Unikatowy prefiks (pozostaw pusty, je\u015bli masz tylko jeden ElkM1).", + "protocol": "Protok\u00f3\u0142", + "temperature_unit": "Jednostka temperatury u\u017cywanej przez ElkM1.", + "username": "Nazwa u\u017cytkownika (tylko bezpieczne)" + }, + "description": "Adres musi by\u0107 w postaci 'adres[:port]' dla tryb\u00f3w 'zabezpieczony' i 'niezabezpieczony'. Przyk\u0142ad: '192.168.1.1'. Port jest opcjonalny i domy\u015blnie ustawiony na 2101 dla po\u0142\u0105cze\u0144 'niezabezpieczonych' i 2601 dla 'zabezpieczonych'. W przypadku protoko\u0142u szeregowego adres musi by\u0107 w formie 'tty[:baudrate]'. Przyk\u0142ad: '/dev/ttyS1'. Warto\u015b\u0107 transmisji jest opcjonalna i domy\u015blnie wynosi 115200.", + "title": "Pod\u0142\u0105czenie do sterownika Elk-M1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/lb.json b/homeassistant/components/emulated_roku/translations/lb.json index e797c38781e..94ff88f9715 100644 --- a/homeassistant/components/emulated_roku/translations/lb.json +++ b/homeassistant/components/emulated_roku/translations/lb.json @@ -17,5 +17,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emul\u00e9ierte Roku" } \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/pl.json b/homeassistant/components/emulated_roku/translations/pl.json index fcc37de1d2c..6ce1c17e5d8 100644 --- a/homeassistant/components/emulated_roku/translations/pl.json +++ b/homeassistant/components/emulated_roku/translations/pl.json @@ -8,7 +8,7 @@ "data": { "advertise_ip": "IP rozg\u0142aszania", "advertise_port": "Port rozg\u0142aszania", - "host_ip": "IP hosta", + "host_ip": "Adres IP", "listen_port": "Port nas\u0142uchu", "name": "Nazwa", "upnp_bind_multicast": "Powi\u0105\u017c multicast (prawda/fa\u0142sz)" diff --git a/homeassistant/components/esphome/translations/lb.json b/homeassistant/components/esphome/translations/lb.json index 8d00a3d98e5..9bfc78af28c 100644 --- a/homeassistant/components/esphome/translations/lb.json +++ b/homeassistant/components/esphome/translations/lb.json @@ -27,7 +27,7 @@ "port": "Port" }, "description": "Gitt Verbindungs Informatioune vun \u00e4rem [ESPHome](https://esphomelib.com/) an.", - "title": "[%key:component::esphome::title%]" + "title": "ESPHome" } } } diff --git a/homeassistant/components/esphome/translations/pl.json b/homeassistant/components/esphome/translations/pl.json index a2e56e09802..c74b3d57e93 100644 --- a/homeassistant/components/esphome/translations/pl.json +++ b/homeassistant/components/esphome/translations/pl.json @@ -23,7 +23,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "port": "Port" }, "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.", diff --git a/homeassistant/components/flume/translations/lb.json b/homeassistant/components/flume/translations/lb.json index aad8a496a11..67f18d61547 100644 --- a/homeassistant/components/flume/translations/lb.json +++ b/homeassistant/components/flume/translations/lb.json @@ -16,6 +16,7 @@ "password": "Passwuert", "username": "Benotzernumm" }, + "description": "Fir k\u00ebnnen op Flume Personal API z'acc\u00e9d\u00e9ieren muss du eng 'Client ID' an eng 'Client Secret' op https://portal.flumetech.com/settings#token ufroen.", "title": "Verbann dech mat dengem Flume Kont." } } diff --git a/homeassistant/components/flume/translations/pl.json b/homeassistant/components/flume/translations/pl.json new file mode 100644 index 00000000000..55dfceac11f --- /dev/null +++ b/homeassistant/components/flume/translations/pl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "To konto jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "client_id": "Identyfikator klienta", + "client_secret": "Has\u0142o klienta", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Aby uzyska\u0107 dost\u0119p do API Flume, musisz poprosi\u0107 o 'ID klienta\u201d i 'klucz tajny klienta' na stronie https://portal.flumetech.com/settings#token", + "title": "Po\u0142\u0105cz z kontem Flume" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/pl.json b/homeassistant/components/flunearyou/translations/pl.json new file mode 100644 index 00000000000..e674d422903 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/pl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane." + }, + "error": { + "general_error": "Nieznany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index 084dd70ee52..9df523af0fb 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "port": "Port" }, "title": "Freebox" diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json new file mode 100644 index 00000000000..50ffd461d4c --- /dev/null +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Ten AVM FRITZ!Box jest ju\u017c skonfigurowany.", + "already_in_progress": "Konfiguracja AVM FRITZ!Box jest ju\u017c w toku.", + "not_found": "W sieci nie znaleziono obs\u0142ugiwanego urz\u0105dzenia AVM FRITZ!Box." + }, + "error": { + "auth_failed": "Nazwa u\u017cytkownika i/lub has\u0142o s\u0105 nieprawid\u0142owe." + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Czy chcesz skonfigurowa\u0107 {name}?", + "title": "AVM FRITZ! Box" + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Wprowad\u017a informacje o urz\u0105dzeniu AVM FRITZ! Box.", + "title": "AVM FRITZ! Box" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/translations/pl.json b/homeassistant/components/glances/translations/pl.json index b0dcfe0c36f..25179e951ad 100644 --- a/homeassistant/components/glances/translations/pl.json +++ b/homeassistant/components/glances/translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", "password": "Has\u0142o", "port": "Port", diff --git a/homeassistant/components/group/translations/lb.json b/homeassistant/components/group/translations/lb.json index 7eab2528c34..aaa9e7b9d81 100644 --- a/homeassistant/components/group/translations/lb.json +++ b/homeassistant/components/group/translations/lb.json @@ -13,5 +13,5 @@ "unlocked": "Net gespaart" } }, - "title": "Gruppe" + "title": "Grupp" } \ No newline at end of file diff --git a/homeassistant/components/heos/translations/pl.json b/homeassistant/components/heos/translations/pl.json index fbba14d88d6..0c0b9ade13f 100644 --- a/homeassistant/components/heos/translations/pl.json +++ b/homeassistant/components/heos/translations/pl.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "access_token": "Host", - "host": "Host" + "access_token": "Nazwa hosta lub adres IP", + "host": "Nazwa hosta lub adres IP" }, "description": "Wprowad\u017a nazw\u0119 hosta lub adres IP urz\u0105dzenia Heos (najlepiej pod\u0142\u0105czonego przewodowo do sieci).", "title": "Po\u0142\u0105czenie z Heos" diff --git a/homeassistant/components/homekit_controller/translations/lb.json b/homeassistant/components/homekit_controller/translations/lb.json index 148d344eaea..c840cb4e9fc 100644 --- a/homeassistant/components/homekit_controller/translations/lb.json +++ b/homeassistant/components/homekit_controller/translations/lb.json @@ -36,5 +36,5 @@ } } }, - "title": "HomeKit Accessoire" + "title": "HomeKit Kontroller" } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index fa9f3f5abc5..b1f635026e6 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "host": "Host" + "host": "Nazwa hosta lub adres IP" }, "title": "Wybierz mostek Hue" }, @@ -26,5 +26,26 @@ "title": "Hub Link" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "pierwszy przycisk", + "button_2": "drugi przycisk", + "button_3": "trzeci przycisk", + "button_4": "czwarty przycisk", + "dim_down": "nast\u0105pi zmniejszenie jasno\u015bci", + "dim_up": "nast\u0105pi zwi\u0119kszenie jasno\u015bci", + "double_buttons_1_3": "Przyciski pierwszy i trzeci", + "double_buttons_2_4": "Przyciski drugi i czwarty", + "turn_off": "Nast\u0105pi wy\u0142\u0105czenie", + "turn_on": "Nast\u0105pi w\u0142\u0105czenie" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_button_short_press": "\"{subtype}\" zostanie naci\u015bni\u0119ty", + "remote_button_short_release": "\"{subtype}\" zostanie zwolniony", + "remote_double_button_long_press": "Obydwa \"{subtype}\" zostanie zwolniony po d\u0142ugim naci\u015bni\u0119ciu", + "remote_double_button_short_press": "Obydwa \"{subtype}\" zostanie zwolniony" + } } } \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/pl.json b/homeassistant/components/icloud/translations/pl.json index a4c7f1f8d9c..20e3f8c2fb4 100644 --- a/homeassistant/components/icloud/translations/pl.json +++ b/homeassistant/components/icloud/translations/pl.json @@ -20,7 +20,7 @@ "user": { "data": { "password": "Has\u0142o", - "username": "E-mail", + "username": "Adres e-mail", "with_family": "Z rodzin\u0105" }, "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 0de012d7163..8bc57941cb6 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -1,7 +1,34 @@ { "config": { "abort": { + "already_configured": "Ta drukarka jest ju\u017c skonfigurowana.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z drukark\u0105.", + "connection_upgrade": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z drukark\u0105 z powodu konieczno\u015bci uaktualnienia po\u0142\u0105czenia.", + "ipp_error": "Wyst\u0105pi\u0142 b\u0142\u0105d IPP.", + "ipp_version_error": "Wersja IPP nie obs\u0142ugiwana przez drukark\u0119.", "parse_error": "Nie mo\u017cna przeanalizowa\u0107 odpowiedzi z drukarki." + }, + "error": { + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z drukark\u0105.", + "connection_upgrade": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z drukark\u0105. Spr\u00f3buj ponownie z zaznaczon\u0105 opcj\u0105 SSL/TLS." + }, + "flow_title": "Drukarka: {name}", + "step": { + "user": { + "data": { + "base_path": "\u015acie\u017cka wzgl\u0119dna do drukarki", + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "ssl": "Drukarka obs\u0142uguje komunikacj\u0119 przez SSL/TLS", + "verify_ssl": "Drukarka u\u017cywa prawid\u0142owego certyfikatu" + }, + "description": "Skonfiguruj drukark\u0119 za pomoc\u0105 protoko\u0142u IPP (Internet Printing Protocol) w celu integracji z Home Assistant.", + "title": "Po\u0142\u0105cz swoj\u0105 drukark\u0119" + }, + "zeroconf_confirm": { + "description": "Czy chcesz doda\u0107 drukark\u0119 o nazwie `{name}` do Home Assistant'a?", + "title": "Wykryto drukark\u0119" + } } } } \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/pl.json b/homeassistant/components/islamic_prayer_times/translations/pl.json new file mode 100644 index 00000000000..a706e6a0596 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Wymagana jest tylko jedna instancja." + }, + "step": { + "user": { + "description": "Czy chcesz skonfigurowa\u0107 Islamskie czasy modlitwy?", + "title": "Skonfiguruj Islamskie czasy modlitwy" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calc_method": "Metoda kalkulacji modlitwy" + } + } + } + }, + "title": "Islamskie czasy modlitwy" +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index f41ceea26f4..860d90536da 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -96,6 +96,7 @@ "data": { "activation": "Wyj\u015bcie, gdy w\u0142\u0105czone", "momentary": "Czas trwania impulsu (ms) (opcjonalnie)", + "more_states": "Konfigurowanie dodatkowych stan\u00f3w dla tej strefy", "name": "Nazwa (opcjonalnie)", "pause": "Przerwa mi\u0119dzy impulsami (ms) (opcjonalnie)", "repeat": "Ilo\u015b\u0107 powt\u00f3rze\u0144 (-1=niesko\u0144czenie) (opcjonalnie)" diff --git a/homeassistant/components/local_ip/translations/pl.json b/homeassistant/components/local_ip/translations/pl.json index c5ae118ef2c..5f8b977dc6e 100644 --- a/homeassistant/components/local_ip/translations/pl.json +++ b/homeassistant/components/local_ip/translations/pl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja z lokalnym adresem IP." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/lock/translations/lb.json b/homeassistant/components/lock/translations/lb.json index 634e51234d7..879f98b6498 100644 --- a/homeassistant/components/lock/translations/lb.json +++ b/homeassistant/components/lock/translations/lb.json @@ -20,5 +20,5 @@ "unlocked": "Net gespaart" } }, - "title": "Schlass" + "title": "Sp\u00e4ren" } \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/lb.json b/homeassistant/components/mailgun/translations/lb.json index e416bdc6a64..10504b88214 100644 --- a/homeassistant/components/mailgun/translations/lb.json +++ b/homeassistant/components/mailgun/translations/lb.json @@ -5,7 +5,7 @@ "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." }, "create_entry": { - "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." + "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." }, "step": { "user": { diff --git a/homeassistant/components/mikrotik/translations/pl.json b/homeassistant/components/mikrotik/translations/pl.json index 44ee1ad4403..20b0348570c 100644 --- a/homeassistant/components/mikrotik/translations/pl.json +++ b/homeassistant/components/mikrotik/translations/pl.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", "password": "Has\u0142o", "port": "Port", diff --git a/homeassistant/components/minecraft_server/translations/pl.json b/homeassistant/components/minecraft_server/translations/pl.json index c5e1abdf729..77d83f37174 100644 --- a/homeassistant/components/minecraft_server/translations/pl.json +++ b/homeassistant/components/minecraft_server/translations/pl.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, "description": "Skonfiguruj instancj\u0119 serwera Minecraft, aby umo\u017cliwi\u0107 monitorowanie.", diff --git a/homeassistant/components/monoprice/translations/pl.json b/homeassistant/components/monoprice/translations/pl.json index d0a3d1751a7..300c19e92b7 100644 --- a/homeassistant/components/monoprice/translations/pl.json +++ b/homeassistant/components/monoprice/translations/pl.json @@ -21,5 +21,20 @@ "title": "Po\u0142\u0105cz z urz\u0105dzeniem" } } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nazwa \u017ar\u00f3d\u0142a #1", + "source_2": "Nazwa \u017ar\u00f3d\u0142a #2", + "source_3": "Nazwa \u017ar\u00f3d\u0142a #3", + "source_4": "Nazwa \u017ar\u00f3d\u0142a #4", + "source_5": "Nazwa \u017ar\u00f3d\u0142a #5", + "source_6": "Nazwa \u017ar\u00f3d\u0142a #6" + }, + "title": "Konfiguruj \u017ar\u00f3d\u0142a" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 94ffb76bb8d..32fab6f325b 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -23,7 +23,7 @@ }, "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", "username": "Nazwa u\u017cytkownika" diff --git a/homeassistant/components/nws/translations/pl.json b/homeassistant/components/nws/translations/pl.json new file mode 100644 index 00000000000..3da4d1f3ea8 --- /dev/null +++ b/homeassistant/components/nws/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API (e-mail)", + "latitude": "Szeroko\u015b\u0107 geograficzna", + "longitude": "D\u0142ugo\u015b\u0107 geograficzna", + "station": "Kod stacji METAR" + }, + "description": "Je\u015bli nie podasz kodu stacji METAR, do znalezienia najbli\u017cszej stacji zostan\u0105 u\u017cyte szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna.", + "title": "Po\u0142\u0105cz z National Weather Service" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/pl.json b/homeassistant/components/owntracks/translations/pl.json index f27d6a35c4d..38d5891edd8 100644 --- a/homeassistant/components/owntracks/translations/pl.json +++ b/homeassistant/components/owntracks/translations/pl.json @@ -4,7 +4,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "\n\nNa Androidzie, otw\u00f3rz [aplikacj\u0119 OwnTracks]({android_url}), id\u017a do: ustawienia -> po\u0142aczenia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: Private HTTP\n - Host: {webhook_url}\n - Identyfikacja:\n - Nazwa u\u017cytkownika: ``\n - ID urz\u0105dzenia: ``\n\nNa iOS, otw\u00f3rz [aplikacj\u0119 OwnTracks]({ios_url}), naci\u015bnij ikon\u0119 (i) w lewym g\u00f3rnym rogu -> ustawienia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: HTTP\n - URL: {webhook_url}\n - W\u0142\u0105cz uwierzytelnianie\n - ID u\u017cytkownika: ``\n\n{secret}\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "\n\nNa Androidzie, otw\u00f3rz [aplikacj\u0119 OwnTracks]({android_url}), id\u017a do: ustawienia -> po\u0142\u0105czenia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: Private HTTP\n - Host: {webhook_url}\n - Identyfikacja:\n - Nazwa u\u017cytkownika: ``\n - ID urz\u0105dzenia: ``\n\nNa iOS, otw\u00f3rz [aplikacj\u0119 OwnTracks]({ios_url}), naci\u015bnij ikon\u0119 (i) w lewym g\u00f3rnym rogu -> ustawienia. Zmie\u0144 nast\u0119puj\u0105ce ustawienia:\n - Tryb: HTTP\n - URL: {webhook_url}\n - W\u0142\u0105cz uwierzytelnianie\n - ID u\u017cytkownika: ``\n\n{secret}\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/panasonic_viera/translations/pl.json b/homeassistant/components/panasonic_viera/translations/pl.json new file mode 100644 index 00000000000..7a880c33265 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/pl.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Ten telewizor Panasonic Viera jest ju\u017c skonfigurowany.", + "not_connected": "Zdalne po\u0142\u0105czenie z telewizorem Panasonic Viera zosta\u0142o utracone. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji.", + "unknown": "Nieznany b\u0142\u0105d, sprawd\u017a logi aby uzyska\u0107 wi\u0119cej szczeg\u00f3\u0142\u00f3w" + }, + "error": { + "invalid_pin_code": "Podany kod PIN jest nieprawid\u0142owy", + "not_connected": "Nie uda\u0142o si\u0119 nawi\u0105za\u0107 zdalnego po\u0142\u0105czenia z telewizorem Panasonic Viera." + }, + "step": { + "pairing": { + "data": { + "pin": "PIN" + }, + "description": "Wprowad\u017a kod PIN wy\u015bwietlony na ekranie telewizora", + "title": "Parowanie" + }, + "user": { + "data": { + "host": "Adres IP", + "name": "Nazwa" + }, + "description": "Wprowad\u017a adres IP telewizora Panasonic Viera", + "title": "Skonfiguruj telewizor" + } + } + }, + "title": "Panasonic Viera" +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index f2a598770a5..2416a2bf7f2 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", - "unknown": "Error inesperat" + "unknown": "Error inesperat", + "wrong_version": "El teu Powerwall utilitza una versi\u00f3 de programari no compatible. L'hauries d'actualitzar o informar d\u2019aquest problema perqu\u00e8 sigui solucionat." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 0489feb2709..2f8ffcf787f 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "No se pudo conectar, por favor int\u00e9ntelo de nuevo", - "unknown": "Error inesperado" + "unknown": "Error inesperado", + "wrong_version": "tu powerwall utiliza una versi\u00f3n de software que no es compatible. Considera actualizar o informar de este problema para que pueda resolverse." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/lb.json b/homeassistant/components/powerwall/translations/lb.json index 9f1e0670d64..1722daf7479 100644 --- a/homeassistant/components/powerwall/translations/lb.json +++ b/homeassistant/components/powerwall/translations/lb.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", - "unknown": "Onerwaarte Feeler" + "unknown": "Onerwaarte Feeler", + "wrong_version": "Deng Powerwall benotzt eng Software Versioun d\u00e9i net \u00ebnnerst\u00ebtzt ass. Betruecht een Upgrade ze maachen oder d\u00ebst ze mellen, sou dass et ka gel\u00e9ist ginn." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index 0c105f26168..a688a9ad8c4 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", - "unknown": "Uventet feil" + "unknown": "Uventet feil", + "wrong_version": "Powerwall bruker en programvareversjon som ikke st\u00f8ttes. Vennligst vurder \u00e5 oppgradere eller rapportere dette problemet, s\u00e5 det kan l\u00f8ses." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index aa4ea4da94f..cfb277179ed 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Niespodziewany b\u0142\u0105d." + "unknown": "Niespodziewany b\u0142\u0105d.", + "wrong_version": "Powerwall u\u017cywa wersji oprogramowania, kt\u00f3ra nie jest obs\u0142ugiwana. Rozwa\u017c uaktualnienie lub zg\u0142oszenie tego problemu, aby mo\u017cna go by\u0142o rozwi\u0105za\u0107." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index 4f5f75dc644..cf3b1cd4ba0 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -5,7 +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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", + "wrong_version": "\u0412\u0430\u0448 powerwall \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u0435 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0435, \u0447\u0442\u043e\u0431\u044b \u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0440\u0435\u0448\u0438\u0442\u044c." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index 91bb4b61801..8883e669a85 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4", + "wrong_version": "\u4e0d\u652f\u63f4\u60a8\u6240\u4f7f\u7528\u7684 Powerwall \u7248\u672c\u3002\u8acb\u8003\u616e\u9032\u884c\u5347\u7d1a\u6216\u56de\u5831\u6b64\u554f\u984c\u3001\u4ee5\u671f\u554f\u984c\u53ef\u4ee5\u7372\u5f97\u89e3\u6c7a\u3002" }, "step": { "user": { diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 70671503e51..2771b3f5b2a 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -14,6 +14,7 @@ "host": "Host Numm oder IP Adresse", "password": "Passwuert" }, + "description": "De Prozess fir BLID an Passwuert opzeruffen ass fir de Moment manuell. Folleg w.e.g. de Schr\u00ebtt d\u00e9i an der Dokumentatioun op https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials beschriwwe sinn.", "title": "Mam Apparat verbannen" } } diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json new file mode 100644 index 00000000000..3772d6a08c5 --- /dev/null +++ b/homeassistant/components/roomba/translations/pl.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "certificate": "Certyfikat", + "continuous": "Ci\u0105g\u0142y", + "delay": "Op\u00f3\u017anienie", + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o" + }, + "description": "Obecnie pobieranie BLID i has\u0142a jest procesem r\u0119cznym. Prosz\u0119 post\u0119powa\u0107 zgodnie z instrukcjami zawartymi w dokumentacji pod adresem: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials.", + "title": "Po\u0142\u0105cz z urz\u0105dzeniem" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Ci\u0105g\u0142y", + "delay": "Op\u00f3\u017anienie" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/lb.json b/homeassistant/components/smartthings/translations/lb.json index 623e00870b8..af1e70eedd8 100644 --- a/homeassistant/components/smartthings/translations/lb.json +++ b/homeassistant/components/smartthings/translations/lb.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "invalid_webhook_url": "Home Assistant ass net richteg konfigur\u00e9iert fir Updates vun SmartThings z'empf\u00e4nken.\nWebhook URL ass ong\u00eblteg:\n>{webhook_url}\n\u00c4nner deng Konfiguratioun sou w\u00e9i an den [Instruktioune]({component_url}) start Home Assistant fr\u00ebsch an prob\u00e9ier nach eemol.", "no_available_locations": "Keng disponibel Smartthings Standuerte fir am Home Assistant anzeriichten." }, "error": { @@ -8,7 +9,7 @@ "token_forbidden": "De Jeton huet net d\u00e9i n\u00e9ideg OAuth M\u00e9iglechkeeten.", "token_invalid_format": "De Jeton muss am UID/GUID Format sinn", "token_unauthorized": "De Jeton ass ong\u00eblteg oder net m\u00e9i autoris\u00e9iert.", - "webhook_error": "SmartThings konnt den an der 'base_url' defin\u00e9ierten Endpoint net valid\u00e9ieren. Iwwerpr\u00e9ift d'Viraussetzunge vun d\u00ebser Komponente" + "webhook_error": "SmartThings konnt d'Webhook URL valid\u00e9ieren. Stell s\u00e9cher dass d'URL vum Internet aus ereechbar ass a prob\u00e9ier nach eemol." }, "step": { "authorize": { @@ -25,11 +26,12 @@ "data": { "location_id": "Standuert" }, + "description": "Wiel SmartThings Standuert aus fir en am Home Assistant dob\u00e4i ze setzen. Dann geht eng nei F\u00ebnster op mat engem Login an fir d'Installatioun vun der Integratioun am Home Assistanr z'erlaaben.", "title": "Standuert auswielen" }, "user": { - "description": "Gitt w.e.g. ee [Pers\u00e9inlechen Acc\u00e8s Jeton]({token_url}) vu SmartThings an dee via [d'Instruktiounen] ({component_url}) erstallt gouf.", - "title": "Pers\u00e9inlechen Acc\u00e8ss Jeton uginn" + "description": "SmartThings g\u00ebtt ageriicht fir Push Aktualis\u00e9ierungen un Home Assistant ze sch\u00e9cken un d'Adresse:\n>{webhook_url}\n\nFalls d\u00ebs net korrekt ass, \u00e4nner deng Konfiguratioun, start Home Assistant fr\u00ebsch a prob\u00e9ier nach emol.", + "title": "Callback URL confirm\u00e9ieren" } } } diff --git a/homeassistant/components/smartthings/translations/pl.json b/homeassistant/components/smartthings/translations/pl.json index 0b6f347873c..517127a5091 100644 --- a/homeassistant/components/smartthings/translations/pl.json +++ b/homeassistant/components/smartthings/translations/pl.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "invalid_webhook_url": "Home Assistant nie jest poprawnie skonfigurowany do otrzymywania danych od SmartThings. Adres URL webhook jest nieprawid\u0142owy: \n > {webhook_url} \n\n Zaktualizuj konfiguracj\u0119 zgodnie z [instrukcj\u0105]({component_url}), uruchom ponownie Home Assistant i spr\u00f3buj ponownie.", + "no_available_locations": "Nie ma dost\u0119pnych lokalizacji SmartThings do skonfigurowania w Home Assistant." + }, "error": { "app_setup_error": "Nie mo\u017cna skonfigurowa\u0107 SmartApp. Spr\u00f3buj ponownie.", "token_forbidden": "Token nie ma wymaganych zakres\u00f3w OAuth.", @@ -8,6 +12,21 @@ "webhook_error": "SmartThings nie mo\u017ce sprawdzi\u0107 poprawno\u015bci punktu ko\u0144cowego skonfigurowanego w `base_url`. Sprawd\u017a wymagania dotycz\u0105ce komponentu." }, "step": { + "authorize": { + "title": "Autoryzuj Home Assistant'a" + }, + "pat": { + "data": { + "access_token": "Token dost\u0119pu" + }, + "title": "Wprowad\u017a osobisty token dost\u0119pu" + }, + "select_location": { + "data": { + "location_id": "Lokalizacja" + }, + "title": "Wybierz lokalizacj\u0119" + }, "user": { "description": "Wprowad\u017a [token dost\u0119pu osobistego]({token_url}) SmartThings, kt\u00f3ry zosta\u0142 utworzony zgodnie z [instrukcj\u0105]({component_url}).", "title": "Wprowad\u017a osobisty token dost\u0119pu" diff --git a/homeassistant/components/soma/translations/pl.json b/homeassistant/components/soma/translations/pl.json index 8d83b05e736..dc71b001bde 100644 --- a/homeassistant/components/soma/translations/pl.json +++ b/homeassistant/components/soma/translations/pl.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "port": "Port" }, "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia SOMA Connect.", diff --git a/homeassistant/components/sun/translations/no.json b/homeassistant/components/sun/translations/no.json index de709024d22..8597fea2ce5 100644 --- a/homeassistant/components/sun/translations/no.json +++ b/homeassistant/components/sun/translations/no.json @@ -1,3 +1,9 @@ { + "state": { + "_": { + "above_horizon": "Over horisonten", + "below_horizon": "Under horisonten" + } + }, "title": "Sol" } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index c2f00b0874c..122f8bef51d 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "El host ya est\u00e1 configurado." }, "error": { + "connection": "Error de conexi\u00f3n: comprueba tu host, puerto y ssl", "login": "Error de inicio de sesi\u00f3n: comprueba tu nombre de usuario y contrase\u00f1a", "missing_data": "Faltan datos: por favor, vuelva a intentarlo m\u00e1s tarde o pruebe con otra configuraci\u00f3n", "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso", diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index 0de4e3bbccd..e7ce33ec43c 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -8,7 +8,7 @@ "missing_data": "Dati mancanti: si prega di riprovare pi\u00f9 tardi o un'altra configurazione", "otp_failed": "Autenticazione in due fasi fallita, riprovare con un nuovo codice di accesso" }, - "flow_title": "Synology DSM {nome} ({host})", + "flow_title": "Synology DSM {name} ({host})", "step": { "2sa": { "data": { @@ -24,7 +24,7 @@ "ssl": "Utilizzare SSL/TLS per connettersi al NAS", "username": "Nome utente" }, - "description": "Vuoi impostare {nome} ({host})?", + "description": "Vuoi impostare {name} ({host})?", "title": "Synology DSM" }, "user": { diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 1db4f2a193c..5453b078a3e 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -4,6 +4,7 @@ "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { + "connection": "Feeler beim verbannen Iwwerpr\u00e9if w.e.g. den Numm, Passwuert & SSL", "login": "Feeler beim Login: iwwerpr\u00e9if de Benotzernumm & Passwuert", "missing_data": "Donn\u00e9\u00ebe feelen, prob\u00e9ier sp\u00e9ider oder mat enger aner Konfiguratioun", "otp_failed": "Feeler mam 2-Faktor-Authentifikatiouns, prob\u00e9ier mat engem neie Code", diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index a10b28b5386..d9e24638f72 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -4,12 +4,13 @@ "already_configured": "Verten er allerede konfigurert" }, "error": { + "connection": "Tilkoblingsfeil: sjekk verten, porten og ssl", "login": "P\u00e5loggingsfeil: Vennligst sjekk brukernavnet ditt og passordet ditt", "missing_data": "Manglende data: Pr\u00f8v p\u00e5 nytt senere eller en annen konfigurasjon", "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode", "unknown": "Ukjent feil: sjekk loggene for \u00e5 f\u00e5 flere detaljer" }, - "flow_title": "Synology DSM {name} ( {host} )", + "flow_title": "Synology DSM {name} ({host})", "step": { "2sa": { "data": { diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index d7045b9d1f6..d7714de27eb 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -4,10 +4,20 @@ "already_configured": "Host jest ju\u017c skonfigurowany." }, "error": { - "login": "B\u0142\u0105d logowania: sprawd\u017a nazw\u0119 u\u017cytkownika i has\u0142o" + "connection": "B\u0142\u0105d po\u0142\u0105czenia: sprawd\u017a host, port i SSL", + "login": "B\u0142\u0105d logowania: sprawd\u017a nazw\u0119 u\u017cytkownika i has\u0142o", + "missing_data": "Brakuj\u0105ce dane: spr\u00f3buj ponownie p\u00f3\u017aniej lub skorzystaj z innej konfiguracji", + "otp_failed": "Uwierzytelnianie dwuetapowe nie powiod\u0142o si\u0119, spr\u00f3buj ponownie z nowym kodem dost\u0119pu", + "unknown": "Nieznany b\u0142\u0105d, sprawd\u017a logi aby uzyska\u0107 wi\u0119cej szczeg\u00f3\u0142\u00f3w" }, "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "Kod" + }, + "title": "Synology DSM: Uwierzytelnianie dwusk\u0142adnikowe" + }, "link": { "data": { "api_version": "Wersja DSM", @@ -22,7 +32,7 @@ "user": { "data": { "api_version": "Wersja DSM", - "host": "Host", + "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port (opcjonalnie)", "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", diff --git a/homeassistant/components/tado/translations/lb.json b/homeassistant/components/tado/translations/lb.json index e84720a2de1..91a81f03177 100644 --- a/homeassistant/components/tado/translations/lb.json +++ b/homeassistant/components/tado/translations/lb.json @@ -25,6 +25,7 @@ "data": { "fallback": "Fallback Modus aktiv\u00e9ieren" }, + "description": "Fallback Modus wiesselt op Smarte Z\u00e4itplang beim n\u00e4chsten ausf\u00e9ieren nodeems eng Zon ausgewielt gouf.", "title": "Tado Optiounen \u00e4nneren" } } diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json new file mode 100644 index 00000000000..65a23d58359 --- /dev/null +++ b/homeassistant/components/tado/translations/pl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "no_homes": "Brak dom\u00f3w powi\u0105zanych z tym kontem Tado.", + "unknown": "Niespodziewany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105cz z kontem Tado" + } + } + }, + "options": { + "step": { + "init": { + "title": "Dostosuj opcje Tado" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/pl.json b/homeassistant/components/tellduslive/translations/pl.json index 3ee83236d7e..c07f9de7b3d 100644 --- a/homeassistant/components/tellduslive/translations/pl.json +++ b/homeassistant/components/tellduslive/translations/pl.json @@ -16,7 +16,7 @@ }, "user": { "data": { - "host": "Host" + "host": "Nazwa hosta lub adres IP" }, "description": "Puste", "title": "Wybierz punkt ko\u0144cowy." diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json new file mode 100644 index 00000000000..58a0f7018da --- /dev/null +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane." + }, + "error": { + "login": "B\u0142\u0105d logowania: sprawd\u017a swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/pl.json b/homeassistant/components/tradfri/translations/pl.json index 379a554e0c3..028956ec6b3 100644 --- a/homeassistant/components/tradfri/translations/pl.json +++ b/homeassistant/components/tradfri/translations/pl.json @@ -12,7 +12,7 @@ "step": { "auth": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "security_code": "Kod bezpiecze\u0144stwa" }, "description": "Mo\u017cesz znale\u017a\u0107 kod bezpiecze\u0144stwa z ty\u0142u bramki.", diff --git a/homeassistant/components/transmission/translations/pl.json b/homeassistant/components/transmission/translations/pl.json index 20b4f8e55a9..52efb32b551 100644 --- a/homeassistant/components/transmission/translations/pl.json +++ b/homeassistant/components/transmission/translations/pl.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", "password": "Has\u0142o", "port": "Port", diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index bc7466a32db..db35a0b3c6a 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -12,7 +12,7 @@ "step": { "user": { "data": { - "host": "Host", + "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", "site": "Identyfikator witryny", @@ -28,7 +28,8 @@ "client_control": { "data": { "block_client": "Klienci z kontrol\u0105 dost\u0119pu do sieci", - "new_client": "Dodaj nowego klienta do kontroli dost\u0119pu do sieci" + "new_client": "Dodaj nowego klienta do kontroli dost\u0119pu do sieci", + "poe_clients": "Zezwalaj na kontrol\u0119 POE klient\u00f3w" }, "description": "Konfigurowanie kontroli klienta\n\nUtw\u00f3rz prze\u0142\u0105czniki dla numer\u00f3w seryjnych, dla kt\u00f3rych chcesz kontrolowa\u0107 dost\u0119p do sieci.", "title": "UniFi opcje 2/3" diff --git a/homeassistant/components/upnp/translations/lb.json b/homeassistant/components/upnp/translations/lb.json index 3e9ef97585b..9fb009f0935 100644 --- a/homeassistant/components/upnp/translations/lb.json +++ b/homeassistant/components/upnp/translations/lb.json @@ -26,7 +26,7 @@ "enable_sensors": "Trafic Sensoren dob\u00e4isetzen", "igd": "UPnP/IGD" }, - "title": "Konfiguratiouns Optiounen fir UPnP/IGD" + "title": "Konfiguratiouns Optiounen" } } } diff --git a/homeassistant/components/vera/translations/pl.json b/homeassistant/components/vera/translations/pl.json new file mode 100644 index 00000000000..baafc324bea --- /dev/null +++ b/homeassistant/components/vera/translations/pl.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Kontroler jest ju\u017c skonfigurowany.", + "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 z kontrolerem za pomoc\u0105 adresu {base_url}" + }, + "step": { + "user": { + "data": { + "exclude": "Identyfikatory urz\u0105dze\u0144 Vera do wykluczenia z Home Assistant.", + "lights": "Identyfikatory prze\u0142\u0105cznik\u00f3w Vera, kt\u00f3re maj\u0105 by\u0107 traktowane jako \u015bwiat\u0142a w Home Assistant.", + "vera_controller_url": "Adres URL kontrolera" + }, + "description": "Podaj adres URL kontrolera Vera. Powinien on wygl\u0105da\u0107 tak: http://192.168.1.161:3480.", + "title": "Skonfiguruj kontroler Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "Identyfikatory urz\u0105dze\u0144 Vera do wykluczenia z Home Assistant.", + "lights": "Identyfikatory prze\u0142\u0105cznik\u00f3w Vera, kt\u00f3re maj\u0105 by\u0107 traktowane jako \u015bwiat\u0142a w Home Assistant." + }, + "description": "Szczeg\u00f3\u0142owe informacje na temat parametr\u00f3w opcjonalnych mo\u017cna znale\u017a\u0107 w dokumentacji Vera: https://www.home-assistant.io/integrations/vera/. Uwaga: Wszelkie zmiany tutaj b\u0119d\u0105 wymaga\u0142y ponownego uruchomienia serwera Home Assistant. Aby wyczy\u015bci\u0107 warto\u015bci, wpisz spacj\u0119.", + "title": "Opcje kontrolera Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/lb.json b/homeassistant/components/vizio/translations/lb.json index 3cd7c1526ee..48267a23126 100644 --- a/homeassistant/components/vizio/translations/lb.json +++ b/homeassistant/components/vizio/translations/lb.json @@ -7,8 +7,8 @@ "error": { "cant_connect": "Konnt sech net mam Apparat verbannen. [Iwwerpr\u00e9ift Dokumentatioun] (https://www.home-assistant.io/integrations/vizio/) a stellt s\u00e9cher dass:\n- Den Apparat ass un\n- Den Apparat ass mam Netzwierk verbonnen\n- D'Optiounen d\u00e9i dir aginn hutt si korrekt\nier dir d'Verbindung nees prob\u00e9iert", "complete_pairing failed": "Feeler beim ofschl\u00e9isse vun der Kopplung. Iwwerpr\u00e9if dass de PIN korrekt an da de Fernsee nach \u00ebmmer ugeschalt a mam Netzwierk verbonnen ass ier de n\u00e4chste Versuch gestart g\u00ebtt.", - "host_exists": "Vizio Apparat mat d\u00ebsem Host ass scho konfigur\u00e9iert.", - "name_exists": "Vizio Apparat mat d\u00ebsen Numm ass scho konfigur\u00e9iert." + "host_exists": "VIZIO Apparat mat d\u00ebsem Host ass scho konfigur\u00e9iert.", + "name_exists": "VIZIO Apparat mat d\u00ebsen Numm ass scho konfigur\u00e9iert." }, "step": { "pair_tv": { @@ -19,11 +19,11 @@ "title": "Kopplungs Prozess ofschl\u00e9issen" }, "pairing_complete": { - "description": "D\u00e4in Visio SmartCast Apparat ass elo mam Home Assistant verbonnen.", + "description": "D\u00e4in VIZIO SmartCast Apparat ass elo mam Home Assistant verbonnen.", "title": "Kopplung ofgeschloss" }, "pairing_complete_import": { - "description": "D\u00e4in Visio SmartCast Apparat ass elo mam Home Assistant verbonnen.\n\nD\u00e4in Acc\u00e8s Jeton ass '**{access_token}**'.", + "description": "D\u00e4in VIZIO SmartCast Apparat ass elo mam Home Assistant verbonnen.\n\nD\u00e4in Acc\u00e8s Jeton ass '**{access_token}**'.", "title": "Kopplung ofgeschloss" }, "user": { @@ -34,7 +34,7 @@ "name": "Numm" }, "description": "Een Access Jeton g\u00ebtt nn\u00ebmme fir Fernseher gebraucht. Wann Dir e Fernseh konfigur\u00e9iert a keen Access Jeton hutt, da loosst et eidel fir duerch dee Pairing Prozess ze goen.", - "title": "Vizo Smartcast Apparat ariichten" + "title": "VIZIO Smartcast Apparat ariichten" } } }, @@ -47,7 +47,7 @@ "volume_step": "Lautst\u00e4erkt Schr\u00ebtt Gr\u00e9isst" }, "description": "Falls du ee Smart TV hues kanns du d'Quelle L\u00ebscht optionell filteren andeems du d'Apps auswiels d\u00e9i soll mat abegraff oder ausgeschloss ginn.", - "title": "Vizo Smartcast Optiounen aktualis\u00e9ieren" + "title": "VIZIO Smartcast Optiounen aktualis\u00e9ieren" } } } diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index 392ae0f47f3..2d586bc3e36 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -6,6 +6,7 @@ }, "error": { "cant_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z urz\u0105dzeniem. [Przejrzyj dokumentacj\u0119] (https://www.home-assistant.io/integrations/vizio/) i ponownie sprawd\u017a, czy: \n - urz\u0105dzenie jest w\u0142\u0105czone,\n - urz\u0105dzenie jest pod\u0142\u0105czone do sieci,\n - wprowadzone warto\u015bci s\u0105 prawid\u0142owe,\n przed pr\u00f3b\u0105 ponownego przes\u0142ania.", + "complete_pairing failed": "Nie mo\u017cna uko\u0144czy\u0107 parowania. Upewnij si\u0119, \u017ce podany kod PIN jest prawid\u0142owy, a telewizor jest zasilany i pod\u0142\u0105czony do sieci przed ponownym przes\u0142aniem.", "host_exists": "Urz\u0105dzenie Vizio z okre\u015blonym hostem jest ju\u017c skonfigurowane.", "name_exists": "Urz\u0105dzenie Vizio o okre\u015blonej nazwie jest ju\u017c skonfigurowane." }, @@ -13,12 +14,16 @@ "pair_tv": { "data": { "pin": "PIN" - } + }, + "description": "Tw\u00f3j telewizor powinien wy\u015bwietla\u0107 kod. Wprowad\u017a ten kod do formularza, a nast\u0119pnie przejd\u017a do nast\u0119pnego kroku, aby zako\u0144czy\u0107 parowanie.", + "title": "Ko\u0144czenie procesu parowania" }, "pairing_complete": { + "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistant'em.", "title": "Parowanie zako\u0144czone" }, "pairing_complete_import": { + "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistant'em.\n\nTw\u00f3j token dost\u0119powy to '**{access_token}**'.", "title": "Parowanie zako\u0144czone" }, "user": { @@ -28,6 +33,7 @@ "host": ":", "name": "Nazwa" }, + "description": "Token dost\u0119powy potrzebny jest tylko dla telewizor\u00f3w. Je\u015bli konfigurujesz telewizor i nie masz jeszcze tokenu dost\u0119powego, pozostaw go pusty aby przej\u015b\u0107 przez proces parowania.", "title": "Konfiguracja klienta Vizio SmartCast" } } diff --git a/homeassistant/components/wled/translations/lb.json b/homeassistant/components/wled/translations/lb.json index 13cfb7bc911..7657e1a12cc 100644 --- a/homeassistant/components/wled/translations/lb.json +++ b/homeassistant/components/wled/translations/lb.json @@ -1,24 +1,24 @@ { "config": { "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "already_configured": "D\u00ebsen WLED Apparat ass scho konfigur\u00e9iert.", + "connection_error": "Feeler beim verbannen mam WLED Apparat." }, "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "connection_error": "Feeler beim verbannen mam WLED Apparat." }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "Numm oder IP Adresse" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "D\u00e4in WLED als Integratioun mam Home Assistant ariichten.", + "title": "D\u00e4in WLED verbannen" }, "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Soll de WLED mam Numm `{name}` am Home Assistant dob\u00e4i gesaat ginn?", + "title": "Entdeckten WLED Apparat" } } } diff --git a/homeassistant/components/zwave/translations/lb.json b/homeassistant/components/zwave/translations/lb.json index 6ef36dff82e..7abe764c2a1 100644 --- a/homeassistant/components/zwave/translations/lb.json +++ b/homeassistant/components/zwave/translations/lb.json @@ -20,14 +20,14 @@ }, "state": { "_": { - "dead": "Net Ereechbar", + "dead": "Doud", "initializing": "Initialis\u00e9iert", "ready": "Bereet", "sleeping": "Schl\u00e9ift" }, "query_stage": { - "dead": "Net Ereechbar ({query_stage})", - "initializing": "Initialis\u00e9iert ( {query_stage} )" + "dead": "Doud", + "initializing": "Initialis\u00e9iert" } } } \ No newline at end of file From c29c0e7e13ea3525e12330a2e518c74469bb565b Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Tue, 28 Apr 2020 16:20:04 +0200 Subject: [PATCH 102/511] Fix atag timezone bug (#34686) Co-authored-by: Boris Nelissen --- homeassistant/components/atag/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 9534bad6df8..902da2bff75 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.2.18"], + "requirements": ["pyatag==0.2.19"], "codeowners": ["@MatsNL"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5f3a3a8977e..46629416aac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1182,7 +1182,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.2.18 +pyatag==0.2.19 # homeassistant.components.netatmo pyatmo==3.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cea7c6df1a4..a9415f05637 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -479,7 +479,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.2.18 +pyatag==0.2.19 # homeassistant.components.netatmo pyatmo==3.3.0 From 454c5d824a04753d13b6496471811930c59665ce Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 28 Apr 2020 10:31:22 -0700 Subject: [PATCH 103/511] Attempt to fix CI (#34800) --- tests/common.py | 2 +- tests/components/hue/test_bridge.py | 29 +- tests/components/mqtt/test_init.py | 12 +- tests/components/mqtt/test_server.py | 11 +- .../components/mqtt_eventstream/test_init.py | 11 +- .../components/mqtt_statestream/test_init.py | 11 +- tests/components/plant/test_init.py | 366 +++++++++--------- tests/components/plex/test_init.py | 7 +- tests/components/plex/test_server.py | 7 +- tests/components/zwave/test_init.py | 12 +- 10 files changed, 224 insertions(+), 244 deletions(-) diff --git a/tests/common.py b/tests/common.py index f39d458bbe0..5a8250e5686 100644 --- a/tests/common.py +++ b/tests/common.py @@ -11,10 +11,10 @@ import logging import os import sys import threading -from unittest.mock import MagicMock, Mock, patch import uuid from aiohttp.test_utils import unused_port as get_test_instance_port # noqa +from asynctest import MagicMock, Mock, patch from homeassistant import auth, config_entries, core as ha, loader from homeassistant.auth import ( diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index c122caf3760..780c77e0196 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -1,18 +1,15 @@ """Test Hue bridge.""" -from unittest.mock import Mock, patch - +from asynctest import CoroutineMock, Mock, patch import pytest from homeassistant.components.hue import bridge, errors from homeassistant.exceptions import ConfigEntryNotReady -from tests.common import mock_coro - async def test_bridge_setup(hass): """Test a successful setup.""" entry = Mock() - api = Mock(initialize=mock_coro) + api = Mock(initialize=CoroutineMock()) entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) @@ -35,9 +32,7 @@ async def test_bridge_setup_invalid_username(hass): with patch.object( bridge, "authenticate_bridge", side_effect=errors.AuthenticationRequired - ), patch.object( - hass.config_entries.flow, "async_init", return_value=mock_coro() - ) as mock_init: + ), patch.object(hass.config_entries.flow, "async_init") as mock_init: assert await hue_bridge.async_setup() is False assert len(mock_init.mock_calls) == 1 @@ -78,18 +73,16 @@ async def test_reset_unloads_entry_if_setup(hass): entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object( - bridge, "authenticate_bridge", return_value=mock_coro(Mock()) - ), patch("aiohue.Bridge", return_value=Mock()), patch.object( - hass.config_entries, "async_forward_entry_setup" - ) as mock_forward: + with patch.object(bridge, "authenticate_bridge", return_value=Mock()), patch( + "aiohue.Bridge", return_value=Mock() + ), patch.object(hass.config_entries, "async_forward_entry_setup") as mock_forward: assert await hue_bridge.async_setup() is True assert len(hass.services.async_services()) == 1 assert len(mock_forward.mock_calls) == 3 with patch.object( - hass.config_entries, "async_forward_entry_unload", return_value=mock_coro(True) + hass.config_entries, "async_forward_entry_unload", return_value=True ) as mock_forward: assert await hue_bridge.async_reset() @@ -99,13 +92,13 @@ async def test_reset_unloads_entry_if_setup(hass): async def test_handle_unauthorized(hass): """Test handling an unauthorized error on update.""" - entry = Mock(async_setup=Mock(return_value=mock_coro(Mock()))) + entry = Mock(async_setup=CoroutineMock()) entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) - with patch.object( - bridge, "authenticate_bridge", return_value=mock_coro(Mock()) - ), patch("aiohue.Bridge", return_value=Mock()): + with patch.object(bridge, "authenticate_bridge", return_value=Mock()), patch( + "aiohue.Bridge", return_value=Mock() + ): assert await hue_bridge.async_setup() is True assert hue_bridge.authorized is True diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index ca5a89a6e63..207529c2ad3 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -34,12 +34,16 @@ from tests.common import ( mock_device_registry, mock_mqtt_component, mock_registry, - mock_storage, threadsafe_coroutine_factory, ) from tests.testing_config.custom_components.test.sensor import DEVICE_CLASSES +@pytest.fixture(autouse=True) +def mock_storage(hass_storage): + """Autouse hass_storage for the TestCase tests.""" + + @pytest.fixture def device_reg(hass): """Return an empty, loaded, registry.""" @@ -87,15 +91,12 @@ class TestMQTTComponent(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() mock_mqtt_component(self.hass) self.calls = [] def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) @callback def record_calls(self, *args): @@ -305,15 +306,12 @@ class TestMQTTCallbacks(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() mock_mqtt_client(self.hass) self.calls = [] def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) @callback def record_calls(self, *args): diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py index a9b5656a0b3..3186b3a2734 100644 --- a/tests/components/mqtt/test_server.py +++ b/tests/components/mqtt/test_server.py @@ -2,12 +2,18 @@ from unittest.mock import MagicMock, Mock from asynctest import CoroutineMock, patch +import pytest import homeassistant.components.mqtt as mqtt from homeassistant.const import CONF_PASSWORD from homeassistant.setup import setup_component -from tests.common import get_test_home_assistant, mock_coro, mock_storage +from tests.common import get_test_home_assistant, mock_coro + + +@pytest.fixture(autouse=True) +def inject_fixture(hass_storage): + """Inject pytest fixtures.""" class TestMQTT: @@ -16,13 +22,10 @@ class TestMQTT: def setup_method(self, method): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() def teardown_method(self, method): """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) @patch("passlib.apps.custom_app_context", Mock(return_value="")) @patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock())) diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index 36698db87e1..eeeab823744 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -2,6 +2,8 @@ import json from unittest.mock import ANY, patch +import pytest + import homeassistant.components.mqtt_eventstream as eventstream from homeassistant.const import EVENT_STATE_CHANGED from homeassistant.core import State, callback @@ -15,24 +17,25 @@ from tests.common import ( get_test_home_assistant, mock_mqtt_component, mock_state_change_event, - mock_storage, ) +@pytest.fixture(autouse=True) +def mock_storage(hass_storage): + """Autouse hass_storage for the TestCase tests.""" + + class TestMqttEventStream: """Test the MQTT eventstream module.""" def setup_method(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() self.mock_mqtt = mock_mqtt_component(self.hass) def teardown_method(self): """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) def add_eventstream(self, sub_topic=None, pub_topic=None, ignore_event=None): """Add a mqtt_eventstream component.""" diff --git a/tests/components/mqtt_statestream/test_init.py b/tests/components/mqtt_statestream/test_init.py index af9a721f1a4..6be413294f1 100644 --- a/tests/components/mqtt_statestream/test_init.py +++ b/tests/components/mqtt_statestream/test_init.py @@ -1,6 +1,8 @@ """The tests for the MQTT statestream component.""" from unittest.mock import ANY, call, patch +import pytest + import homeassistant.components.mqtt_statestream as statestream from homeassistant.core import State from homeassistant.setup import setup_component @@ -9,24 +11,25 @@ from tests.common import ( get_test_home_assistant, mock_mqtt_component, mock_state_change_event, - mock_storage, ) +@pytest.fixture(autouse=True) +def mock_storage(hass_storage): + """Autouse hass_storage for the TestCase tests.""" + + class TestMqttStateStream: """Test the MQTT statestream module.""" def setup_method(self): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() self.mock_mqtt = mock_mqtt_component(self.hass) def teardown_method(self): """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) def add_statestream( self, diff --git a/tests/components/plant/test_init.py b/tests/components/plant/test_init.py index 801061e94ea..f7260a5cf2e 100644 --- a/tests/components/plant/test_init.py +++ b/tests/components/plant/test_init.py @@ -1,7 +1,5 @@ """Unit tests for platform/plant.py.""" -import asyncio from datetime import datetime, timedelta -import unittest import pytest @@ -15,9 +13,10 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) -from homeassistant.setup import setup_component +from homeassistant.core import State +from homeassistant.setup import async_setup_component -from tests.common import get_test_home_assistant, init_recorder_component +from tests.common import init_recorder_component GOOD_DATA = { "moisture": 50, @@ -47,206 +46,193 @@ GOOD_CONFIG = { } -class _MockState: - def __init__(self, state=None): - self.state = state - - -class TestPlant(unittest.TestCase): - """Tests for component "plant".""" - - def setUp(self): - """Create test instance of Home Assistant.""" - self.hass = get_test_home_assistant() - self.hass.start() - - def tearDown(self): - """Stop everything that was started.""" - self.hass.stop() - - @asyncio.coroutine - def test_valid_data(self): - """Test processing valid data.""" - sensor = plant.Plant("my plant", GOOD_CONFIG) - sensor.hass = self.hass - for reading, value in GOOD_DATA.items(): - sensor.state_changed( - GOOD_CONFIG["sensors"][reading], None, _MockState(value) - ) - assert sensor.state == "ok" - attrib = sensor.state_attributes - for reading, value in GOOD_DATA.items(): - # battery level has a different name in - # the JSON format than in hass - assert attrib[reading] == value - - @asyncio.coroutine - def test_low_battery(self): - """Test processing with low battery data and limit set.""" - sensor = plant.Plant("other plant", GOOD_CONFIG) - sensor.hass = self.hass - assert sensor.state_attributes["problem"] == "none" +async def test_valid_data(hass): + """Test processing valid data.""" + sensor = plant.Plant("my plant", GOOD_CONFIG) + sensor.entity_id = "sensor.mqtt_plant_battery" + sensor.hass = hass + for reading, value in GOOD_DATA.items(): sensor.state_changed( - "sensor.mqtt_plant_battery", _MockState(45), _MockState(10) + GOOD_CONFIG["sensors"][reading], + None, + State(GOOD_CONFIG["sensors"][reading], value), ) - assert sensor.state == "problem" - assert sensor.state_attributes["problem"] == "battery low" + assert sensor.state == "ok" + attrib = sensor.state_attributes + for reading, value in GOOD_DATA.items(): + # battery level has a different name in + # the JSON format than in hass + assert attrib[reading] == value - def test_initial_states(self): - """Test plant initialises attributes if sensor already exists.""" - self.hass.states.set( - MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} - ) - plant_name = "some_plant" - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} - ) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert 5 == state.attributes[plant.READING_MOISTURE] - def test_update_states(self): - """Test updating the state of a sensor. - - Make sure that plant processes this correctly. - """ - plant_name = "some_plant" - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} - ) - self.hass.states.set( - MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} - ) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state - assert 5 == state.attributes[plant.READING_MOISTURE] - - def test_unavailable_state(self): - """Test updating the state with unavailable. - - Make sure that plant processes this correctly. - """ - plant_name = "some_plant" - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} - ) - self.hass.states.set( - MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} - ) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert state.state == STATE_PROBLEM - assert state.attributes[plant.READING_MOISTURE] == STATE_UNAVAILABLE - - def test_state_problem_if_unavailable(self): - """Test updating the state with unavailable after setting it to valid value. - - Make sure that plant processes this correctly. - """ - plant_name = "some_plant" - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} - ) - self.hass.states.set( - MOISTURE_ENTITY, 42, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} - ) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert state.state == STATE_OK - assert state.attributes[plant.READING_MOISTURE] == 42 - self.hass.states.set( - MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} - ) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert state.state == STATE_PROBLEM - assert state.attributes[plant.READING_MOISTURE] == STATE_UNAVAILABLE - - @pytest.mark.skipif( - plant.ENABLE_LOAD_HISTORY is False, - reason="tests for loading from DB are unstable, thus" - "this feature is turned of until tests become" - "stable", +async def test_low_battery(hass): + """Test processing with low battery data and limit set.""" + sensor = plant.Plant("other plant", GOOD_CONFIG) + sensor.entity_id = "sensor.mqtt_plant_battery" + sensor.hass = hass + assert sensor.state_attributes["problem"] == "none" + sensor.state_changed( + "sensor.mqtt_plant_battery", + State("sensor.mqtt_plant_battery", 45), + State("sensor.mqtt_plant_battery", 10), ) - def test_load_from_db(self): - """Test bootstrapping the brightness history from the database. + assert sensor.state == "problem" + assert sensor.state_attributes["problem"] == "battery low" - This test can should only be executed if the loading of the history - is enabled via plant.ENABLE_LOAD_HISTORY. - """ - init_recorder_component(self.hass) - plant_name = "wise_plant" - for value in [20, 30, 10]: - self.hass.states.set( - BRIGHTNESS_ENTITY, value, {ATTR_UNIT_OF_MEASUREMENT: "Lux"} - ) - self.hass.block_till_done() - # wait for the recorder to really store the data - self.hass.data[recorder.DATA_INSTANCE].block_till_done() +async def test_initial_states(hass): + """Test plant initialises attributes if sensor already exists.""" + hass.states.async_set(MOISTURE_ENTITY, 5, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}) + plant_name = "some_plant" + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert 5 == state.attributes[plant.READING_MOISTURE] - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + +async def test_update_states(hass): + """Test updating the state of a sensor. + + Make sure that plant processes this correctly. + """ + plant_name = "some_plant" + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + 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] + + +async def test_unavailable_state(hass): + """Test updating the state with unavailable. + + Make sure that plant processes this correctly. + """ + plant_name = "some_plant" + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + hass.states.async_set( + MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} + ) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert state.state == STATE_PROBLEM + assert state.attributes[plant.READING_MOISTURE] == STATE_UNAVAILABLE + + +async def test_state_problem_if_unavailable(hass): + """Test updating the state with unavailable after setting it to valid value. + + Make sure that plant processes this correctly. + """ + plant_name = "some_plant" + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + hass.states.async_set(MOISTURE_ENTITY, 42, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY}) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert state.state == STATE_OK + assert state.attributes[plant.READING_MOISTURE] == 42 + hass.states.async_set( + MOISTURE_ENTITY, STATE_UNAVAILABLE, {ATTR_UNIT_OF_MEASUREMENT: CONDUCTIVITY} + ) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert state.state == STATE_PROBLEM + assert state.attributes[plant.READING_MOISTURE] == STATE_UNAVAILABLE + + +@pytest.mark.skipif( + plant.ENABLE_LOAD_HISTORY is False, + reason="tests for loading from DB are unstable, thus" + "this feature is turned of until tests become" + "stable", +) +async def test_load_from_db(hass): + """Test bootstrapping the brightness history from the database. + + This test can should only be executed if the loading of the history + is enabled via plant.ENABLE_LOAD_HISTORY. + """ + init_recorder_component(hass) + plant_name = "wise_plant" + for value in [20, 30, 10]: + + hass.states.async_set( + BRIGHTNESS_ENTITY, value, {ATTR_UNIT_OF_MEASUREMENT: "Lux"} ) - self.hass.block_till_done() + await hass.async_block_till_done() + # wait for the recorder to really store the data + hass.data[recorder.DATA_INSTANCE].block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert STATE_UNKNOWN == state.state - max_brightness = state.attributes.get(plant.ATTR_MAX_BRIGHTNESS_HISTORY) - assert 30 == max_brightness + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + await hass.async_block_till_done() - def test_brightness_history(self): - """Test the min_brightness check.""" - plant_name = "some_plant" - assert setup_component( - self.hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} - ) - self.hass.states.set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert STATE_PROBLEM == state.state - - self.hass.states.set(BRIGHTNESS_ENTITY, 600, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state - - self.hass.states.set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) - self.hass.block_till_done() - state = self.hass.states.get(f"plant.{plant_name}") - assert STATE_OK == state.state + state = hass.states.get(f"plant.{plant_name}") + assert STATE_UNKNOWN == state.state + max_brightness = state.attributes.get(plant.ATTR_MAX_BRIGHTNESS_HISTORY) + assert 30 == max_brightness -class TestDailyHistory(unittest.TestCase): - """Test the DailyHistory helper class.""" +async def test_brightness_history(hass): + """Test the min_brightness check.""" + plant_name = "some_plant" + assert await async_setup_component( + hass, plant.DOMAIN, {plant.DOMAIN: {plant_name: GOOD_CONFIG}} + ) + hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert STATE_PROBLEM == state.state - def test_no_data(self): - """Test with empty history.""" - dh = plant.DailyHistory(3) - assert dh.max is None + hass.states.async_set(BRIGHTNESS_ENTITY, 600, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert STATE_OK == state.state - def test_one_day(self): - """Test storing data for the same day.""" - dh = plant.DailyHistory(3) - values = [-2, 10, 0, 5, 20] - for i in range(len(values)): - dh.add_measurement(values[i]) - max_value = max(values[0 : i + 1]) - assert 1 == len(dh._days) - assert dh.max == max_value + hass.states.async_set(BRIGHTNESS_ENTITY, 100, {ATTR_UNIT_OF_MEASUREMENT: "lux"}) + await hass.async_block_till_done() + state = hass.states.get(f"plant.{plant_name}") + assert STATE_OK == state.state - def test_multiple_days(self): - """Test storing data for different days.""" - dh = plant.DailyHistory(3) - today = datetime.now() - today_minus_1 = today - timedelta(days=1) - today_minus_2 = today_minus_1 - timedelta(days=1) - today_minus_3 = today_minus_2 - timedelta(days=1) - days = [today_minus_3, today_minus_2, today_minus_1, today] - values = [10, 1, 7, 3] - max_values = [10, 10, 10, 7] - for i in range(len(days)): - dh.add_measurement(values[i], days[i]) - assert max_values[i] == dh.max +def test_daily_history_no_data(hass): + """Test with empty history.""" + dh = plant.DailyHistory(3) + assert dh.max is None + + +def test_daily_history_one_day(hass): + """Test storing data for the same day.""" + dh = plant.DailyHistory(3) + values = [-2, 10, 0, 5, 20] + for i in range(len(values)): + dh.add_measurement(values[i]) + max_value = max(values[0 : i + 1]) + assert 1 == len(dh._days) + assert dh.max == max_value + + +def test_daily_history_multiple_days(hass): + """Test storing data for different days.""" + dh = plant.DailyHistory(3) + today = datetime.now() + today_minus_1 = today - timedelta(days=1) + today_minus_2 = today_minus_1 - timedelta(days=1) + today_minus_3 = today_minus_2 - timedelta(days=1) + days = [today_minus_3, today_minus_2, today_minus_1, today] + values = [10, 1, 7, 3] + max_values = [10, 10, 10, 7] + + for i in range(len(days)): + dh.add_measurement(values[i], days[i]) + assert max_values[i] == dh.max diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index ef2199b11c5..09a2e8f6ada 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -35,7 +35,6 @@ from tests.common import ( MockConfigEntry, async_fire_time_changed, async_test_home_assistant, - mock_storage, ) @@ -74,24 +73,22 @@ async def test_setup_with_config(hass): assert loaded_server.plex_server == mock_plex_server +@pytest.mark.skip class TestClockedPlex(ClockedTestCase): """Create clock-controlled asynctest class.""" @pytest.fixture(autouse=True) - def inject_fixture(self, caplog): + def inject_fixture(self, caplog, hass_storage): """Inject pytest fixtures as instance attributes.""" self.caplog = caplog async def setUp(self): """Initialize this test class.""" self.hass = await async_test_home_assistant(self.loop) - self.mock_storage = mock_storage() - self.mock_storage.__enter__() async def tearDown(self): """Clean up the HomeAssistant instance.""" await self.hass.async_stop() - self.mock_storage.__exit__(None, None, None) async def test_setup_with_config_entry(self): """Test setup component with config.""" diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index bc3f9e39fe0..b3cbd6c79d4 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -2,6 +2,7 @@ import copy from asynctest import ClockedTestCase, patch +import pytest from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.plex.const import ( @@ -17,7 +18,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DEFAULT_DATA, DEFAULT_OPTIONS from .mock_classes import MockPlexServer -from tests.common import MockConfigEntry, async_test_home_assistant, mock_storage +from tests.common import MockConfigEntry, async_test_home_assistant async def test_new_users_available(hass): @@ -107,19 +108,17 @@ async def test_new_ignored_users_available(hass, caplog): assert sensor.state == str(len(mock_plex_server.accounts)) +@pytest.mark.skip class TestClockedPlex(ClockedTestCase): """Create clock-controlled asynctest class.""" async def setUp(self): """Initialize this test class.""" self.hass = await async_test_home_assistant(self.loop) - self.mock_storage = mock_storage() - self.mock_storage.__enter__() async def tearDown(self): """Clean up the HomeAssistant instance.""" await self.hass.async_stop() - self.mock_storage.__exit__(None, None, None) async def test_mark_sessions_idle(self): """Test marking media_players as idle when sessions end.""" diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 71f7ea17f76..e14162d5788 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -28,11 +28,15 @@ from tests.common import ( get_test_home_assistant, mock_coro, mock_registry, - mock_storage, ) from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue +@pytest.fixture(autouse=True) +def mock_storage(hass_storage): + """Autouse hass_storage for the TestCase tests.""" + + async def test_valid_device_config(hass, mock_openzwave): """Test valid device config.""" device_config = {"light.kitchen": {"ignored": "true"}} @@ -829,8 +833,6 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() self.hass.start() self.registry = mock_registry(self.hass) @@ -866,7 +868,6 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" self.hass.stop() - self.mock_storage.__exit__(None, None, None) @patch.object(zwave, "import_module") @patch.object(zwave, "discovery") @@ -1199,8 +1200,6 @@ class TestZWaveServices(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" self.hass = get_test_home_assistant() - self.mock_storage = mock_storage() - self.mock_storage.__enter__() self.hass.start() # Initialize zwave @@ -1216,7 +1215,6 @@ class TestZWaveServices(unittest.TestCase): self.hass.services.call("zwave", "stop_network", {}) self.hass.block_till_done() self.hass.stop() - self.mock_storage.__exit__(None, None, None) def test_add_node(self): """Test zwave add_node service.""" From c97ce05b09416400bc50d619acbccbddca9691f2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 28 Apr 2020 10:35:38 -0700 Subject: [PATCH 104/511] Add script to copy backend translations to frontend (#34706) --- script/translations/download.py | 23 +++-------------- script/translations/frontend.py | 46 +++++++++++++++++++++++++++++++++ script/translations/migrate.py | 32 ++++++++--------------- script/translations/util.py | 2 +- 4 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 script/translations/frontend.py diff --git a/script/translations/download.py b/script/translations/download.py index 0e8c0664ecb..364f309b644 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 """Merge all translation sources into a single JSON file.""" -import glob import json import os import pathlib @@ -47,16 +46,6 @@ def run_download_docker(): raise ExitApp("Failed to download translations") -def load_json(filename: str) -> Union[List, Dict]: - """Load JSON data from a file and return as dict or list. - - Defaults to returning empty dict if file is not found. - """ - with open(filename, encoding="utf-8") as fdesc: - return json.loads(fdesc.read()) - return {} - - def save_json(filename: str, data: Union[List, Dict]): """Save JSON data to a file. @@ -69,11 +58,6 @@ def save_json(filename: str, data: Union[List, Dict]): return False -def get_language(path): - """Get the language code for the given file path.""" - return os.path.splitext(os.path.basename(path))[0] - - def get_component_path(lang, component): """Get the component translation path.""" if os.path.isdir(os.path.join("homeassistant", "components", component)): @@ -132,10 +116,9 @@ def save_language_translations(lang, translations): def write_integration_translations(): """Write integration translations.""" - paths = glob.iglob("build/translations-download/*.json") - for path in paths: - lang = get_language(path) - translations = load_json(path) + for lang_file in DOWNLOAD_DIR.glob("*.json"): + lang = lang_file.stem + translations = json.loads(lang_file.read_text()) save_language_translations(lang, translations) diff --git a/script/translations/frontend.py b/script/translations/frontend.py new file mode 100644 index 00000000000..c955c240478 --- /dev/null +++ b/script/translations/frontend.py @@ -0,0 +1,46 @@ +"""Write updated translations to the frontend.""" +import argparse +import json + +from .const import FRONTEND_DIR +from .download import DOWNLOAD_DIR, run_download_docker +from .util import get_base_arg_parser + +FRONTEND_BACKEND_TRANSLATIONS = FRONTEND_DIR / "translations/backend" + + +def get_arguments() -> argparse.Namespace: + """Get parsed passed in arguments.""" + parser = get_base_arg_parser() + parser.add_argument( + "--skip-download", action="store_true", help="Skip downloading translations." + ) + return parser.parse_args() + + +def run(): + """Update frontend translations with backend data. + + We use the downloaded Docker files because it gives us each language in 1 file. + """ + args = get_arguments() + + if not args.skip_download: + run_download_docker() + + for lang_file in DOWNLOAD_DIR.glob("*.json"): + translations = json.loads(lang_file.read_text()) + + to_write_translations = {"component": {}} + + for domain, domain_translations in translations["component"].items(): + if "state" not in domain_translations: + continue + + to_write_translations["component"][domain] = { + "state": domain_translations["state"] + } + + (FRONTEND_BACKEND_TRANSLATIONS / lang_file.name).write_text( + json.dumps(to_write_translations, indent=2) + ) diff --git a/script/translations/migrate.py b/script/translations/migrate.py index fcf44e3dece..8081a442955 100644 --- a/script/translations/migrate.py +++ b/script/translations/migrate.py @@ -327,27 +327,17 @@ def find_frontend_states(): def run(): """Migrate translations.""" # Import new common keys - # migrate_project_keys_translations( - # FRONTEND_PROJECT_ID, - # CORE_PROJECT_ID, - # { - # "state::default::off": "common::state::off", - # "state::default::on": "common::state::on", - # "state::cover::open": "common::state::open", - # "state::cover::closed": "common::state::closed", - # "state::binary_sensor::connectivity::on": "common::state::connected", - # "state::binary_sensor::connectivity::off": "common::state::disconnected", - # "state::lock::locked": "common::state::locked", - # "state::lock::unlocked": "common::state::unlocked", - # "state::timer::active": "common::state::active", - # "state::camera::idle": "common::state::idle", - # "state::media_player::standby": "common::state::standby", - # "state::media_player::paused": "common::state::paused", - # "state::device_tracker::home": "common::state::home", - # "state::device_tracker::not_home": "common::state::not_home", - # }, - # ) + migrate_project_keys_translations( + CORE_PROJECT_ID, + FRONTEND_PROJECT_ID, + { + "common::state::off": "state::default::off", + "common::state::on": "state::default::on", + }, + ) - find_frontend_states() + # find_frontend_states() + + # find_different_languages() return 0 diff --git a/script/translations/util.py b/script/translations/util.py index 2cc7dfff689..9839fefd9d5 100644 --- a/script/translations/util.py +++ b/script/translations/util.py @@ -13,7 +13,7 @@ def get_base_arg_parser() -> argparse.ArgumentParser: parser.add_argument( "action", type=str, - choices=["clean", "develop", "download", "migrate", "upload"], + choices=["clean", "develop", "download", "frontend", "migrate", "upload"], ) parser.add_argument("--debug", action="store_true", help="Enable log output") return parser From 893f796df2b3243852f70341c8557109dcc33a80 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 28 Apr 2020 22:06:08 +0200 Subject: [PATCH 105/511] deCONZ - device triggers for Aqara Opple switches (#34815) * Added Aqara Opple device triggers * Update homeassistant/components/deconz/device_trigger.py Co-Authored-By: Robert Svensson * Update homeassistant/components/deconz/device_trigger.py Co-Authored-By: Robert Svensson * Update homeassistant/components/deconz/device_trigger.py Co-Authored-By: Robert Svensson * Fix flake8 Co-authored-by: Robert Svensson --- .../components/deconz/device_trigger.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index a146552f14e..b0486a99dc8 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -300,6 +300,50 @@ AQARA_SQUARE_SWITCH_WXKG11LM_2016 = { (CONF_QUADRUPLE_PRESS, CONF_TURN_ON): {CONF_EVENT: 1006}, } +AQARA_OPPLE_2_BUTTONS_MODEL = "lumi.remote.b286opcn01" +AQARA_OPPLE_2_BUTTONS = { + (CONF_LONG_PRESS, CONF_TURN_OFF): {CONF_EVENT: 1001}, + (CONF_SHORT_RELEASE, CONF_TURN_OFF): {CONF_EVENT: 1002}, + (CONF_LONG_RELEASE, CONF_TURN_OFF): {CONF_EVENT: 1003}, + (CONF_DOUBLE_PRESS, CONF_TURN_OFF): {CONF_EVENT: 1004}, + (CONF_TRIPLE_PRESS, CONF_TURN_OFF): {CONF_EVENT: 1005}, + (CONF_LONG_PRESS, CONF_TURN_ON): {CONF_EVENT: 2001}, + (CONF_SHORT_RELEASE, CONF_TURN_ON): {CONF_EVENT: 2002}, + (CONF_LONG_RELEASE, CONF_TURN_ON): {CONF_EVENT: 2003}, + (CONF_DOUBLE_PRESS, CONF_TURN_ON): {CONF_EVENT: 2004}, + (CONF_TRIPLE_PRESS, CONF_TURN_ON): {CONF_EVENT: 2005}, +} + +AQARA_OPPLE_4_BUTTONS_MODEL = "lumi.remote.b486opcn01" +AQARA_OPPLE_4_BUTTONS = { + **AQARA_OPPLE_2_BUTTONS, + (CONF_LONG_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 3001}, + (CONF_SHORT_RELEASE, CONF_DIM_DOWN): {CONF_EVENT: 3002}, + (CONF_LONG_RELEASE, CONF_DIM_DOWN): {CONF_EVENT: 3003}, + (CONF_DOUBLE_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 3004}, + (CONF_TRIPLE_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 3005}, + (CONF_LONG_PRESS, CONF_DIM_UP): {CONF_EVENT: 4001}, + (CONF_SHORT_RELEASE, CONF_DIM_UP): {CONF_EVENT: 4002}, + (CONF_LONG_RELEASE, CONF_DIM_UP): {CONF_EVENT: 4003}, + (CONF_DOUBLE_PRESS, CONF_DIM_UP): {CONF_EVENT: 4004}, + (CONF_TRIPLE_PRESS, CONF_DIM_UP): {CONF_EVENT: 4005}, +} + +AQARA_OPPLE_6_BUTTONS_MODEL = "lumi.remote.b686opcn01" +AQARA_OPPLE_6_BUTTONS = { + **AQARA_OPPLE_4_BUTTONS, + (CONF_LONG_PRESS, CONF_DIM_DOWN): {CONF_EVENT: 5001}, + (CONF_SHORT_RELEASE, CONF_LEFT): {CONF_EVENT: 5002}, + (CONF_LONG_RELEASE, CONF_LEFT): {CONF_EVENT: 5003}, + (CONF_DOUBLE_PRESS, CONF_LEFT): {CONF_EVENT: 5004}, + (CONF_TRIPLE_PRESS, CONF_LEFT): {CONF_EVENT: 5005}, + (CONF_LONG_PRESS, CONF_RIGHT): {CONF_EVENT: 6001}, + (CONF_SHORT_RELEASE, CONF_RIGHT): {CONF_EVENT: 6002}, + (CONF_LONG_RELEASE, CONF_RIGHT): {CONF_EVENT: 6003}, + (CONF_DOUBLE_PRESS, CONF_RIGHT): {CONF_EVENT: 6004}, + (CONF_TRIPLE_PRESS, CONF_RIGHT): {CONF_EVENT: 6005}, +} + REMOTES = { HUE_DIMMER_REMOTE_MODEL_GEN1: HUE_DIMMER_REMOTE, HUE_DIMMER_REMOTE_MODEL_GEN2: HUE_DIMMER_REMOTE, @@ -319,6 +363,9 @@ REMOTES = { AQARA_ROUND_SWITCH_MODEL: AQARA_ROUND_SWITCH, AQARA_SQUARE_SWITCH_MODEL: AQARA_SQUARE_SWITCH, AQARA_SQUARE_SWITCH_WXKG11LM_2016_MODEL: AQARA_SQUARE_SWITCH_WXKG11LM_2016, + AQARA_OPPLE_2_BUTTONS_MODEL: AQARA_OPPLE_2_BUTTONS, + AQARA_OPPLE_4_BUTTONS_MODEL: AQARA_OPPLE_4_BUTTONS, + AQARA_OPPLE_6_BUTTONS_MODEL: AQARA_OPPLE_6_BUTTONS, } TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( From 28f6e79385092d263c1b99a8c28c1190923f7de8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 28 Apr 2020 14:31:16 -0700 Subject: [PATCH 106/511] Parallelize collections helper (#34783) --- homeassistant/helpers/collection.py | 40 +++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index e720887eb70..06c86d3aa1c 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -1,5 +1,6 @@ """Helper to deal with YAML + storage.""" from abc import ABC, abstractmethod +import asyncio import logging from typing import Any, Awaitable, Callable, Dict, List, Optional, cast @@ -107,8 +108,9 @@ class ObservableCollection(ABC): async def notify_change(self, change_type: str, item_id: str, item: dict) -> None: """Notify listeners of a change.""" self.logger.debug("%s %s: %s", change_type, item_id, item) - for listener in self.listeners: - await listener(change_type, item_id, item) + await asyncio.gather( + *[listener(change_type, item_id, item) for listener in self.listeners] + ) class YamlCollection(ObservableCollection): @@ -118,6 +120,8 @@ class YamlCollection(ObservableCollection): """Load the YAML collection. Overrides existing data.""" old_ids = set(self.data) + tasks = [] + for item in data: item_id = item[CONF_ID] @@ -131,11 +135,15 @@ class YamlCollection(ObservableCollection): event = CHANGE_ADDED self.data[item_id] = item - await self.notify_change(event, item_id, item) + tasks.append(self.notify_change(event, item_id, item)) for item_id in old_ids: + tasks.append( + self.notify_change(CHANGE_REMOVED, item_id, self.data.pop(item_id)) + ) - await self.notify_change(CHANGE_REMOVED, item_id, self.data.pop(item_id)) + if tasks: + await asyncio.gather(*tasks) class StorageCollection(ObservableCollection): @@ -169,7 +177,13 @@ class StorageCollection(ObservableCollection): for item in raw_storage["items"]: self.data[item[CONF_ID]] = item - await self.notify_change(CHANGE_ADDED, item[CONF_ID], item) + + await asyncio.gather( + *[ + self.notify_change(CHANGE_ADDED, item[CONF_ID], item) + for item in raw_storage["items"] + ] + ) @abstractmethod async def _process_create_data(self, data: dict) -> dict: @@ -240,8 +254,12 @@ class IDLessCollection(ObservableCollection): async def async_load(self, data: List[dict]) -> None: """Load the collection. Overrides existing data.""" - for item_id, item in list(self.data.items()): - await self.notify_change(CHANGE_REMOVED, item_id, item) + await asyncio.gather( + *[ + self.notify_change(CHANGE_REMOVED, item_id, item) + for item_id, item in list(self.data.items()) + ] + ) self.data.clear() @@ -250,7 +268,13 @@ class IDLessCollection(ObservableCollection): item_id = f"fakeid-{self.counter}" self.data[item_id] = item - await self.notify_change(CHANGE_ADDED, item_id, item) + + await asyncio.gather( + *[ + self.notify_change(CHANGE_ADDED, item_id, item) + for item_id, item in self.data.items() + ] + ) @callback From 87801d8acaa532200d080d7a67ef9ae12748aa76 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 28 Apr 2020 14:31:25 -0700 Subject: [PATCH 107/511] Minor helpers cleanup (#34786) --- homeassistant/helpers/entity_component.py | 8 +++++++- homeassistant/helpers/service.py | 20 +++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 4c30457b62c..0a7c52f7059 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -270,9 +270,15 @@ class EntityComponent: async def async_remove_entity(self, entity_id: str) -> None: """Remove an entity managed by one of the platforms.""" + found = None + for platform in self._platforms.values(): if entity_id in platform.entities: - await platform.async_remove_entity(entity_id) + found = platform + break + + if found: + await found.async_remove_entity(entity_id) async def async_prepare_reload(self, *, skip_reset: bool = False) -> Optional[dict]: """Prepare reloading this entity component. diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index a75f862467e..ce52d188540 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -545,19 +545,25 @@ def verify_domain_control(hass: HomeAssistantType, domain: str) -> Callable: reg = await hass.helpers.entity_registry.async_get_registry() + authorized = False + for entity in reg.entities.values(): if entity.platform != domain: continue if user.permissions.check_entity(entity.entity_id, POLICY_CONTROL): - return await service_handler(call) + authorized = True + break - raise Unauthorized( - context=call.context, - permission=POLICY_CONTROL, - user_id=call.context.user_id, - perm_category=CAT_ENTITIES, - ) + if not authorized: + raise Unauthorized( + context=call.context, + permission=POLICY_CONTROL, + user_id=call.context.user_id, + perm_category=CAT_ENTITIES, + ) + + return await service_handler(call) return check_permissions From 0246761f943c6bfac2d6f2883d3472e74f2964a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 28 Apr 2020 14:31:35 -0700 Subject: [PATCH 108/511] Log threading exceptions properly (#34789) --- homeassistant/bootstrap.py | 4 ++++ homeassistant/core.py | 2 ++ homeassistant/util/thread.py | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 homeassistant/util/thread.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 618a168be61..d53d86f528c 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -249,6 +249,10 @@ def async_enable_logging( logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("aiohttp.access").setLevel(logging.WARNING) + sys.excepthook = lambda *args: logging.getLogger(None).exception( + "Uncaught exception", exc_info=args # type: ignore + ) + # Log errors to a file if we have write access to file or config dir if log_file is None: err_log_path = hass.config.path(ERROR_LOG_FILENAME) diff --git a/homeassistant/core.py b/homeassistant/core.py index c799656df89..045e56ecb53 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -71,6 +71,7 @@ from homeassistant.exceptions import ( from homeassistant.util import location from homeassistant.util.async_ import fire_coroutine_threadsafe, run_callback_threadsafe import homeassistant.util.dt as dt_util +from homeassistant.util.thread import fix_threading_exception_logging from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM, UnitSystem # Typing imports that create a circular dependency @@ -80,6 +81,7 @@ if TYPE_CHECKING: block_async_io.enable() +fix_threading_exception_logging() # pylint: disable=invalid-name T = TypeVar("T") diff --git a/homeassistant/util/thread.py b/homeassistant/util/thread.py new file mode 100644 index 00000000000..e5654e6f8c6 --- /dev/null +++ b/homeassistant/util/thread.py @@ -0,0 +1,26 @@ +"""Threading util helpers.""" +import sys +import threading +from typing import Any + + +def fix_threading_exception_logging() -> None: + """Fix threads passing uncaught exceptions to our exception hook. + + https://bugs.python.org/issue1230540 + Fixed in Python 3.8. + """ + if sys.version_info[:2] >= (3, 8): + return + + run_old = threading.Thread.run + + def run(*args: Any, **kwargs: Any) -> None: + try: + run_old(*args, **kwargs) + except (KeyboardInterrupt, SystemExit): # pylint: disable=try-except-raise + raise + except Exception: # pylint: disable=broad-except + sys.excepthook(*sys.exc_info()) + + threading.Thread.run = run # type: ignore From 2f3bd80de41ff494be31fadbede92ee188f1e95c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Apr 2020 17:10:23 -0500 Subject: [PATCH 109/511] Add missing blocks (#34832) --- tests/components/homekit/test_homekit.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 7ba51106cb9..b63ee6d0bd9 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -133,6 +133,7 @@ async def test_setup_auto_start_disabled(hass): homekit.status = STATUS_READY await hass.services.async_call(DOMAIN, SERVICE_HOMEKIT_START, blocking=True) + await hass.async_block_till_done() assert homekit.async_start.called is True # Test start call with driver started @@ -141,6 +142,7 @@ async def test_setup_auto_start_disabled(hass): homekit.status = STATUS_STOPPED await hass.services.async_call(DOMAIN, SERVICE_HOMEKIT_START, blocking=True) + await hass.async_block_till_done() assert homekit.async_start.called is False @@ -326,6 +328,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): ) as hk_driver_start: await homekit.async_start() + await hass.async_block_till_done() mock_add_acc.assert_called_with(state) mock_setup_msg.assert_called_with(hass, pin, ANY) hk_driver_add_acc.assert_called_with(homekit.bridge) @@ -335,6 +338,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): # Test start() if already started hk_driver_start.reset_mock() await homekit.async_start() + await hass.async_block_till_done() assert not hk_driver_start.called @@ -362,6 +366,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p ) as hk_driver_start: await homekit.async_start() + await hass.async_block_till_done() mock_setup_msg.assert_called_with(hass, pin, ANY) hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called @@ -370,6 +375,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p # Test start() if already started hk_driver_start.reset_mock() await homekit.async_start() + await hass.async_block_till_done() assert not hk_driver_start.called @@ -508,6 +514,7 @@ async def test_homekit_finds_linked_batteries( "pyhap.accessory_driver.AccessoryDriver.start" ): await homekit.async_start() + await hass.async_block_till_done() mock_get_acc.assert_called_with( hass, From a217630fdd3c8237c3e12bbb871f58653637b30f Mon Sep 17 00:00:00 2001 From: Quentame Date: Wed, 29 Apr 2020 00:23:39 +0200 Subject: [PATCH 110/511] Bump python-synology to 0.7.2 (#34830) --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index f3fd52a74a5..5834217b6ea 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.1"], + "requirements": ["python-synology==0.7.2"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 46629416aac..50e3c6dcdef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1683,7 +1683,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.1 +python-synology==0.7.2 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a9415f05637..ffc8cf7eb6d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -650,7 +650,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.1 +python-synology==0.7.2 # homeassistant.components.tado python-tado==0.8.1 From ed7cacbf92f305b003ed5d830f9b3634628836f2 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 28 Apr 2020 22:52:27 +0000 Subject: [PATCH 111/511] Removed defunct punctuality from nederlandse_spoorwegen (#34680) --- homeassistant/components/nederlandse_spoorwegen/sensor.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/homeassistant/components/nederlandse_spoorwegen/sensor.py b/homeassistant/components/nederlandse_spoorwegen/sensor.py index 2db46b02db4..39c05ff7cbf 100644 --- a/homeassistant/components/nederlandse_spoorwegen/sensor.py +++ b/homeassistant/components/nederlandse_spoorwegen/sensor.py @@ -148,7 +148,6 @@ class NSDepartureSensor(Entity): "arrival_platform_planned": self._trips[0].arrival_platform_planned, "arrival_platform_actual": self._trips[0].arrival_platform_actual, "next": None, - "punctuality": None, "status": self._trips[0].status.lower(), "transfers": self._trips[0].nr_transfers, "route": route, @@ -197,10 +196,6 @@ class NSDepartureSensor(Entity): ): attributes["arrival_delay"] = True - # Punctuality attributes - if self._trips[0].punctuality is not None: - attributes["punctuality"] = self._trips[0].punctuality - # Next attributes if len(self._trips) > 1: if self._trips[1].departure_time_actual is not None: From 26241980d76781104e267da7255fc5ef1345021b Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 28 Apr 2020 22:56:04 +0000 Subject: [PATCH 112/511] Update nederlandse_spoorwegen nsapi to 3.0.4 (#34681) * NS version bumped to 3.0.4 --- homeassistant/components/nederlandse_spoorwegen/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nederlandse_spoorwegen/manifest.json b/homeassistant/components/nederlandse_spoorwegen/manifest.json index 10291802fed..01372e744fb 100644 --- a/homeassistant/components/nederlandse_spoorwegen/manifest.json +++ b/homeassistant/components/nederlandse_spoorwegen/manifest.json @@ -2,6 +2,6 @@ "domain": "nederlandse_spoorwegen", "name": "Nederlandse Spoorwegen (NS)", "documentation": "https://www.home-assistant.io/integrations/nederlandse_spoorwegen", - "requirements": ["nsapi==3.0.3"], + "requirements": ["nsapi==3.0.4"], "codeowners": ["@YarmoM"] } diff --git a/requirements_all.txt b/requirements_all.txt index 50e3c6dcdef..5aa9301a6f1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -943,7 +943,7 @@ niko-home-control==0.2.1 niluclient==0.1.2 # homeassistant.components.nederlandse_spoorwegen -nsapi==3.0.3 +nsapi==3.0.4 # homeassistant.components.nsw_fuel_station nsw-fuel-api-client==1.0.10 From e46f1b69ba3620fbb6cc0c602ea6efce8fe2b11b Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Wed, 29 Apr 2020 01:44:31 +0200 Subject: [PATCH 113/511] Add Xiaomi miio Alarm Control Panel (#32091) Co-Authored-By: Martin Hjelmare --- .coveragerc | 12 +- .../components/xiaomi_miio/__init__.py | 68 ++++++++ .../xiaomi_miio/alarm_control_panel.py | 150 ++++++++++++++++++ .../components/xiaomi_miio/config_flow.py | 82 ++++++++++ .../components/xiaomi_miio/gateway.py | 47 ++++++ .../components/xiaomi_miio/manifest.json | 3 +- .../components/xiaomi_miio/strings.json | 29 ++++ .../xiaomi_miio/translations/en.json | 29 ++++ homeassistant/generated/config_flows.py | 1 + .../xiaomi_miio/test_config_flow.py | 126 +++++++++++++++ 10 files changed, 545 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/xiaomi_miio/alarm_control_panel.py create mode 100644 homeassistant/components/xiaomi_miio/config_flow.py create mode 100644 homeassistant/components/xiaomi_miio/gateway.py create mode 100644 homeassistant/components/xiaomi_miio/strings.json create mode 100644 homeassistant/components/xiaomi_miio/translations/en.json create mode 100644 tests/components/xiaomi_miio/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index bec37425416..2aabc0d8028 100644 --- a/.coveragerc +++ b/.coveragerc @@ -829,7 +829,17 @@ omit = homeassistant/components/xfinity/device_tracker.py homeassistant/components/xiaomi/camera.py homeassistant/components/xiaomi_aqara/* - homeassistant/components/xiaomi_miio/* + homeassistant/components/xiaomi_miio/__init__.py + homeassistant/components/xiaomi_miio/air_quality.py + homeassistant/components/xiaomi_miio/alarm_control_panel.py + homeassistant/components/xiaomi_miio/device_tracker.py + homeassistant/components/xiaomi_miio/fan.py + homeassistant/components/xiaomi_miio/gateway.py + homeassistant/components/xiaomi_miio/light.py + homeassistant/components/xiaomi_miio/remote.py + homeassistant/components/xiaomi_miio/sensor.py + homeassistant/components/xiaomi_miio/switch.py + homeassistant/components/xiaomi_miio/vacuum.py homeassistant/components/xiaomi_tv/media_player.py homeassistant/components/xmpp/notify.py homeassistant/components/xs1/* diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 9abc871b9b4..0dd03e42e7d 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -1 +1,69 @@ """Support for Xiaomi Miio.""" +import logging + +from homeassistant import config_entries, core +from homeassistant.const import CONF_HOST, CONF_TOKEN +from homeassistant.helpers import device_registry as dr + +from .config_flow import CONF_FLOW_TYPE, CONF_GATEWAY +from .const import DOMAIN +from .gateway import ConnectXiaomiGateway + +_LOGGER = logging.getLogger(__name__) + +GATEWAY_PLATFORMS = ["alarm_control_panel"] + + +async def async_setup(hass: core.HomeAssistant, config: dict): + """Set up the Xiaomi Miio component.""" + return True + + +async def async_setup_entry( + hass: core.HomeAssistant, entry: config_entries.ConfigEntry +): + """Set up the Xiaomi Miio components from a config entry.""" + hass.data[DOMAIN] = {} + if entry.data[CONF_FLOW_TYPE] == CONF_GATEWAY: + if not await async_setup_gateway_entry(hass, entry): + return False + + return True + + +async def async_setup_gateway_entry( + hass: core.HomeAssistant, entry: config_entries.ConfigEntry +): + """Set up the Xiaomi Gateway component from a config entry.""" + host = entry.data[CONF_HOST] + token = entry.data[CONF_TOKEN] + name = entry.title + gateway_id = entry.data["gateway_id"] + + # Connect to gateway + gateway = ConnectXiaomiGateway(hass) + if not await gateway.async_connect_gateway(host, token): + return False + gateway_info = gateway.gateway_info + + hass.data[DOMAIN][entry.entry_id] = gateway.gateway_device + + gateway_model = f"{gateway_info.model}-{gateway_info.hardware_version}" + + device_registry = await dr.async_get_registry(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections={(dr.CONNECTION_NETWORK_MAC, gateway_info.mac_address)}, + identifiers={(DOMAIN, gateway_id)}, + manufacturer="Xiaomi", + name=name, + model=gateway_model, + sw_version=gateway_info.firmware_version, + ) + + for component in GATEWAY_PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py new file mode 100644 index 00000000000..dccd94dc963 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -0,0 +1,150 @@ +"""Support for Xiomi Gateway alarm control panels.""" + +from functools import partial +import logging + +from miio import DeviceException + +from homeassistant.components.alarm_control_panel import ( + SUPPORT_ALARM_ARM_AWAY, + AlarmControlPanelEntity, +) +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMING, + STATE_ALARM_DISARMED, +) + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +XIAOMI_STATE_ARMED_VALUE = "on" +XIAOMI_STATE_DISARMED_VALUE = "off" +XIAOMI_STATE_ARMING_VALUE = "oning" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Xiaomi Gateway Alarm from a config entry.""" + entities = [] + gateway = hass.data[DOMAIN][config_entry.entry_id] + entity = XiaomiGatewayAlarm( + gateway, + f"{config_entry.title} Alarm", + config_entry.data["model"], + config_entry.data["mac"], + config_entry.data["gateway_id"], + ) + entities.append(entity) + async_add_entities(entities) + + +class XiaomiGatewayAlarm(AlarmControlPanelEntity): + """Representation of the XiaomiGatewayAlarm.""" + + def __init__( + self, gateway_device, gateway_name, model, mac_address, gateway_device_id + ): + """Initialize the entity.""" + self._gateway = gateway_device + self._name = gateway_name + self._gateway_device_id = gateway_device_id + self._unique_id = f"{model}-{mac_address}" + self._icon = "mdi:shield-home" + self._available = None + self._state = None + + @property + def unique_id(self): + """Return an unique ID.""" + return self._unique_id + + @property + def device_id(self): + """Return the device id of the gateway.""" + return self._gateway_device_id + + @property + def device_info(self): + """Return the device info of the gateway.""" + return { + "identifiers": {(DOMAIN, self._gateway_device_id)}, + } + + @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.""" + return self._icon + + @property + def available(self): + """Return true when state is known.""" + return self._available + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def supported_features(self) -> int: + """Return the list of supported features.""" + return SUPPORT_ALARM_ARM_AWAY + + async def _try_command(self, mask_error, func, *args, **kwargs): + """Call a device command handling error messages.""" + try: + result = await self.hass.async_add_executor_job( + partial(func, *args, **kwargs) + ) + _LOGGER.debug("Response received from miio device: %s", result) + except DeviceException as exc: + _LOGGER.error(mask_error, exc) + + async def async_alarm_arm_away(self, code=None): + """Turn on.""" + await self._try_command( + "Turning the alarm on failed: %s", self._gateway.alarm.on + ) + + async def async_alarm_disarm(self, code=None): + """Turn off.""" + await self._try_command( + "Turning the alarm off failed: %s", self._gateway.alarm.off + ) + + async def async_update(self): + """Fetch state from the device.""" + try: + state = await self.hass.async_add_executor_job(self._gateway.alarm.status) + except DeviceException as ex: + self._available = False + _LOGGER.error("Got exception while fetching the state: %s", ex) + return + + _LOGGER.debug("Got new state: %s", state) + + self._available = True + + if state == XIAOMI_STATE_ARMED_VALUE: + self._state = STATE_ALARM_ARMED_AWAY + elif state == XIAOMI_STATE_DISARMED_VALUE: + self._state = STATE_ALARM_DISARMED + elif state == XIAOMI_STATE_ARMING_VALUE: + self._state = STATE_ALARM_ARMING + else: + _LOGGER.warning( + "New state (%s) doesn't match expected values: %s/%s/%s", + state, + XIAOMI_STATE_ARMED_VALUE, + XIAOMI_STATE_DISARMED_VALUE, + XIAOMI_STATE_ARMING_VALUE, + ) + self._state = None + + _LOGGER.debug("State value: %s", self._state) diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py new file mode 100644 index 00000000000..092f5d85d30 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -0,0 +1,82 @@ +"""Config flow to configure Xiaomi Miio.""" +import logging + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN + +# pylint: disable=unused-import +from .const import DOMAIN +from .gateway import ConnectXiaomiGateway + +_LOGGER = logging.getLogger(__name__) + +CONF_FLOW_TYPE = "config_flow_device" +CONF_GATEWAY = "gateway" +DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" + +GATEWAY_CONFIG = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), + vol.Optional(CONF_NAME, default=DEFAULT_GATEWAY_NAME): str, + } +) + +CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_GATEWAY, default=False): bool}) + + +class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Xiaomi Miio config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + errors = {} + if user_input is not None: + # Check which device needs to be connected. + if user_input[CONF_GATEWAY]: + return await self.async_step_gateway() + + errors["base"] = "no_device_selected" + + return self.async_show_form( + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors + ) + + async def async_step_gateway(self, user_input=None): + """Handle a flow initialized by the user to configure a gateway.""" + errors = {} + if user_input is not None: + host = user_input[CONF_HOST] + token = user_input[CONF_TOKEN] + + # Try to connect to a Xiaomi Gateway. + connect_gateway_class = ConnectXiaomiGateway(self.hass) + await connect_gateway_class.async_connect_gateway(host, token) + gateway_info = connect_gateway_class.gateway_info + + if gateway_info is not None: + unique_id = f"{gateway_info.model}-{gateway_info.mac_address}-gateway" + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + return self.async_create_entry( + title=user_input[CONF_NAME], + data={ + CONF_FLOW_TYPE: CONF_GATEWAY, + CONF_HOST: host, + CONF_TOKEN: token, + "gateway_id": unique_id, + "model": gateway_info.model, + "mac": gateway_info.mac_address, + }, + ) + + errors["base"] = "connect_error" + + return self.async_show_form( + step_id="gateway", data_schema=GATEWAY_CONFIG, errors=errors + ) diff --git a/homeassistant/components/xiaomi_miio/gateway.py b/homeassistant/components/xiaomi_miio/gateway.py new file mode 100644 index 00000000000..2195c9eecdc --- /dev/null +++ b/homeassistant/components/xiaomi_miio/gateway.py @@ -0,0 +1,47 @@ +"""Code to handle a Xiaomi Gateway.""" +import logging + +from miio import DeviceException, gateway + +_LOGGER = logging.getLogger(__name__) + + +class ConnectXiaomiGateway: + """Class to async connect to a Xiaomi Gateway.""" + + def __init__(self, hass): + """Initialize the entity.""" + self._hass = hass + self._gateway_device = None + self._gateway_info = None + + @property + def gateway_device(self): + """Return the class containing all connections to the gateway.""" + return self._gateway_device + + @property + def gateway_info(self): + """Return the class containing gateway info.""" + return self._gateway_info + + async def async_connect_gateway(self, host, token): + """Connect to the Xiaomi Gateway.""" + _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) + try: + self._gateway_device = gateway.Gateway(host, token) + self._gateway_info = await self._hass.async_add_executor_job( + self._gateway_device.info + ) + except DeviceException: + _LOGGER.error( + "DeviceException during setup of xiaomi gateway with host %s", host + ) + return False + _LOGGER.debug( + "%s %s %s detected", + self._gateway_info.model, + self._gateway_info.firmware_version, + self._gateway_info.hardware_version, + ) + return True diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 1db01321285..468389b4626 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -1,6 +1,7 @@ { "domain": "xiaomi_miio", - "name": "Xiaomi miio", + "name": "Xiaomi Miio", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", "requirements": ["construct==2.9.45", "python-miio==0.5.0.1"], "codeowners": ["@rytilahti", "@syssi"] diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json new file mode 100644 index 00000000000..1562bbb6526 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "title": "Xiaomi Miio", + "description": "Select to which device you want to connect.", + "data": { + "gateway": "Connect to a Xiaomi Gateway" + } + }, + "gateway": { + "title": "Connect to a Xiaomi Gateway", + "description": "You will need the API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions.", + "data": { + "host": "IP adress", + "token": "API Token", + "name": "Name of the Gateway" + } + } + }, + "error": { + "connect_error": "Failed to connect, please try again", + "no_device_selected": "No device selected, please select one device." + }, + "abort": { + "already_configured": "Device is already configured" + } + } +} diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json new file mode 100644 index 00000000000..1562bbb6526 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "title": "Xiaomi Miio", + "description": "Select to which device you want to connect.", + "data": { + "gateway": "Connect to a Xiaomi Gateway" + } + }, + "gateway": { + "title": "Connect to a Xiaomi Gateway", + "description": "You will need the API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions.", + "data": { + "host": "IP adress", + "token": "API Token", + "name": "Name of the Gateway" + } + } + }, + "error": { + "connect_error": "Failed to connect, please try again", + "no_device_selected": "No device selected, please select one device." + }, + "abort": { + "already_configured": "Device is already configured" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e17aefac636..636dab4de27 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -141,6 +141,7 @@ FLOWS = [ "withings", "wled", "wwlln", + "xiaomi_miio", "zha", "zwave" ] diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py new file mode 100644 index 00000000000..2c1411ded68 --- /dev/null +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -0,0 +1,126 @@ +"""Test the Xiaomi Miio config flow.""" +from unittest.mock import Mock + +from asynctest import patch +from miio import DeviceException + +from homeassistant import config_entries +from homeassistant.components.xiaomi_miio import config_flow, const +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN + +TEST_HOST = "1.2.3.4" +TEST_TOKEN = "12345678901234567890123456789012" +TEST_NAME = "Test_Gateway" +TEST_MODEL = "model5" +TEST_MAC = "AB-CD-EF-GH-IJ-KL" +TEST_GATEWAY_ID = f"{TEST_MODEL}-{TEST_MAC}-gateway" +TEST_HARDWARE_VERSION = "AB123" +TEST_FIRMWARE_VERSION = "1.2.3_456" + + +def get_mock_info( + model=TEST_MODEL, + mac_address=TEST_MAC, + hardware_version=TEST_HARDWARE_VERSION, + firmware_version=TEST_FIRMWARE_VERSION, +): + """Return a mock gateway info instance.""" + gateway_info = Mock() + gateway_info.model = model + gateway_info.mac_address = mac_address + gateway_info.hardware_version = hardware_version + gateway_info.firmware_version = firmware_version + + return gateway_info + + +async def test_config_flow_step_user_no_device(hass): + """Test config flow, user step with no device selected.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {},) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {"base": "no_device_selected"} + + +async def test_config_flow_step_gateway_connect_error(hass): + """Test config flow, gateway connection error.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {config_flow.CONF_GATEWAY: True}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "gateway" + assert result["errors"] == {} + + with patch( + "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info", + side_effect=DeviceException({}), + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "gateway" + assert result["errors"] == {"base": "connect_error"} + + +async def test_config_flow_gateway_success(hass): + """Test a successful config flow.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"] == {} + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {config_flow.CONF_GATEWAY: True}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "gateway" + assert result["errors"] == {} + + mock_info = get_mock_info() + + with patch( + "homeassistant.components.xiaomi_miio.gateway.gateway.Gateway.info", + return_value=mock_info, + ), patch( + "homeassistant.components.xiaomi_miio.async_setup_entry", return_value=True + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: TEST_HOST, CONF_NAME: TEST_NAME, CONF_TOKEN: TEST_TOKEN}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == TEST_NAME + assert result["data"] == { + config_flow.CONF_FLOW_TYPE: config_flow.CONF_GATEWAY, + CONF_HOST: TEST_HOST, + CONF_TOKEN: TEST_TOKEN, + "gateway_id": TEST_GATEWAY_ID, + "model": TEST_MODEL, + "mac": TEST_MAC, + } From 5825cd7868abbbfa00c57f6e2398ee2204acd16b Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 29 Apr 2020 01:49:43 +0200 Subject: [PATCH 114/511] Fix async_setup type in components/homeassistant module (#34816) --- homeassistant/components/homeassistant/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/homeassistant/__init__.py b/homeassistant/components/homeassistant/__init__.py index 7a0ae33345a..e0a4d88ec6a 100644 --- a/homeassistant/components/homeassistant/__init__.py +++ b/homeassistant/components/homeassistant/__init__.py @@ -2,7 +2,6 @@ import asyncio import itertools as it import logging -from typing import Awaitable import voluptuous as vol @@ -33,7 +32,7 @@ SERVICE_SET_LOCATION = "set_location" SCHEMA_UPDATE_ENTITY = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) -async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: +async def async_setup(hass: ha.HomeAssistant, config: dict) -> bool: """Set up general services related to Home Assistant.""" async def async_handle_turn_service(service): From b9bd757df1d6baabeefde6109448e95434ea83e5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 29 Apr 2020 01:59:25 +0200 Subject: [PATCH 115/511] Fix meteoalarm exception handling with instance of KeyError (#34828) --- homeassistant/components/meteoalarm/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index ebdeaa7c903..b481b417b9e 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -42,7 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: api = Meteoalert(country, province, language) - except KeyError(): + except KeyError: _LOGGER.error("Wrong country digits or province name") return From 7faba60e836e0e01ddb564fc0e737b1416b7c9b1 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 29 Apr 2020 00:03:31 +0000 Subject: [PATCH 116/511] [ci skip] Translation update --- .../components/airvisual/translations/it.json | 30 +++++++++-- .../components/airvisual/translations/nl.json | 29 +++++++++++ .../components/airvisual/translations/no.json | 4 +- .../alarm_control_panel/translations/no.json | 6 +-- .../components/almond/translations/no.json | 2 +- .../components/arcam_fmj/translations/no.json | 2 +- .../components/atag/translations/it.json | 20 +++++++ .../components/atag/translations/no.json | 2 +- .../binary_sensor/translations/no.json | 4 +- .../components/braviatv/translations/nl.json | 27 ++++++++++ .../components/braviatv/translations/no.json | 2 +- .../components/brother/translations/no.json | 4 +- .../components/camera/translations/it.json | 2 +- .../components/camera/translations/no.json | 1 + .../components/climate/translations/no.json | 13 ++--- .../components/cover/translations/it.json | 2 +- .../components/cover/translations/no.json | 16 +++--- .../components/deconz/translations/nl.json | 2 + .../components/deconz/translations/no.json | 16 +++--- .../components/demo/translations/no.json | 2 +- .../device_tracker/translations/no.json | 6 +++ .../components/doorbird/translations/nl.json | 14 +++++ .../components/elgato/translations/no.json | 2 +- .../components/elkm1/translations/nl.json | 18 +++++++ .../components/esphome/translations/no.json | 2 +- .../components/fan/translations/no.json | 6 +++ .../components/flume/translations/nl.json | 1 + .../flunearyou/translations/nl.json | 18 +++++++ .../components/fritzbox/translations/it.json | 32 ++++++++++++ .../components/fritzbox/translations/no.json | 6 +-- .../geonetnz_quakes/translations/no.json | 4 +- .../geonetnz_volcano/translations/no.json | 2 +- .../components/gios/translations/no.json | 2 +- .../components/group/translations/no.json | 8 +++ .../components/harmony/translations/no.json | 2 +- .../components/hassio/translations/no.json | 2 +- .../hisense_aehw4a1/translations/no.json | 2 +- .../components/hue/translations/nl.json | 10 ++++ .../input_boolean/translations/no.json | 6 +++ .../components/ipp/translations/nl.json | 11 +++- .../components/ipp/translations/no.json | 2 +- .../islamic_prayer_times/translations/it.json | 23 ++++++++ .../components/konnected/translations/nl.json | 1 + .../components/light/translations/nl.json | 1 + .../components/light/translations/no.json | 8 ++- .../components/linky/translations/no.json | 2 +- .../components/lovelace/translations/no.json | 2 +- .../lutron_caseta/translations/no.json | 2 +- .../media_player/translations/no.json | 7 ++- .../components/met/translations/no.json | 2 +- .../components/monoprice/translations/nl.json | 34 ++++++++++++ .../components/monoprice/translations/no.json | 2 +- .../components/myq/translations/nl.json | 21 ++++++++ .../components/nexia/translations/nl.json | 13 +++++ .../components/nut/translations/nl.json | 6 +++ .../components/nut/translations/no.json | 4 +- .../components/nws/translations/nl.json | 2 + .../opentherm_gw/translations/no.json | 2 +- .../panasonic_viera/translations/no.json | 4 +- .../components/person/translations/no.json | 8 ++- .../components/plant/translations/no.json | 6 +++ .../components/powerwall/translations/es.json | 2 +- .../components/powerwall/translations/it.json | 3 +- .../components/ps4/translations/no.json | 4 +- .../pvpc_hourly_pricing/translations/nl.json | 11 ++++ .../components/remote/translations/no.json | 6 +++ .../components/roku/translations/no.json | 4 +- .../components/scene/translations/no.json | 2 +- .../components/script/translations/no.json | 8 ++- .../season/translations/sensor.it.json | 6 +++ .../components/sensor/translations/no.json | 8 ++- .../simplisafe/translations/ca.json | 3 +- .../simplisafe/translations/es.json | 1 + .../simplisafe/translations/it.json | 3 +- .../simplisafe/translations/lb.json | 1 + .../simplisafe/translations/nl.json | 1 + .../simplisafe/translations/no.json | 1 + .../simplisafe/translations/ru.json | 1 + .../simplisafe/translations/zh-Hant.json | 3 +- .../smartthings/translations/nl.json | 9 ++++ .../components/starline/translations/no.json | 4 +- .../components/switch/translations/no.json | 6 +++ .../synology_dsm/translations/it.json | 4 +- .../synology_dsm/translations/nl.json | 9 +++- .../synology_dsm/translations/no.json | 6 +-- .../components/tado/translations/nl.json | 11 ++++ .../components/timer/translations/no.json | 9 ++++ .../totalconnect/translations/nl.json | 18 +++++++ .../totalconnect/translations/no.json | 2 +- .../twentemilieu/translations/no.json | 2 +- .../components/unifi/translations/nl.json | 1 + .../components/unifi/translations/no.json | 6 +++ .../components/upnp/translations/it.json | 2 +- .../components/vacuum/translations/no.json | 12 +++-- .../components/vera/translations/nl.json | 21 ++++++++ .../components/vizio/translations/no.json | 2 +- .../components/weather/translations/it.json | 2 +- .../components/wemo/translations/no.json | 2 +- .../components/wled/translations/no.json | 2 +- .../xiaomi_miio/translations/en.json | 52 +++++++++---------- .../components/zwave/translations/it.json | 6 +-- .../components/zwave/translations/nl.json | 4 +- .../components/zwave/translations/no.json | 4 ++ 103 files changed, 626 insertions(+), 130 deletions(-) create mode 100644 homeassistant/components/airvisual/translations/nl.json create mode 100644 homeassistant/components/atag/translations/it.json create mode 100644 homeassistant/components/braviatv/translations/nl.json create mode 100644 homeassistant/components/doorbird/translations/nl.json create mode 100644 homeassistant/components/elkm1/translations/nl.json create mode 100644 homeassistant/components/flunearyou/translations/nl.json create mode 100644 homeassistant/components/fritzbox/translations/it.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/it.json create mode 100644 homeassistant/components/monoprice/translations/nl.json create mode 100644 homeassistant/components/myq/translations/nl.json create mode 100644 homeassistant/components/nexia/translations/nl.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/nl.json create mode 100644 homeassistant/components/tado/translations/nl.json create mode 100644 homeassistant/components/timer/translations/no.json create mode 100644 homeassistant/components/totalconnect/translations/nl.json create mode 100644 homeassistant/components/vera/translations/nl.json diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index 268c82bfccf..9831011c7b2 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -1,19 +1,41 @@ { "config": { "abort": { - "already_configured": "Queste coordinate sono gi\u00e0 state registrate." + "already_configured": "Queste coordinate o Node/Pro ID sono gi\u00e0 registrate." }, "error": { - "invalid_api_key": "Chiave API non valida" + "general_error": "Si \u00e8 verificato un errore sconosciuto.", + "invalid_api_key": "Chiave API non valida fornita.", + "unable_to_connect": "Impossibile connettersi all'unit\u00e0 Node/Pro." }, "step": { - "user": { + "geography": { "data": { "api_key": "Chiave API", "latitude": "Latitudine", "longitude": "Logitudine" }, - "description": "Monitorare la qualit\u00e0 dell'aria in una posizione geografica.", + "description": "Utilizzare l'API di AirVisual cloud per monitorare una posizione geografica.", + "title": "Configurare una Geografia" + }, + "node_pro": { + "data": { + "ip_address": "Indirizzo IP/Nome host dell'unit\u00e0", + "password": "Password dell'unit\u00e0" + }, + "description": "Monitorare un'unit\u00e0 AirVisual personale. La password pu\u00f2 essere recuperata dall'interfaccia utente dell'unit\u00e0.", + "title": "Configurare un AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "Chiave API", + "cloud_api": "Posizione geografica", + "latitude": "Latitudine", + "longitude": "Logitudine", + "node_pro": "AirVisual Node Pro", + "type": "Tipo di integrazione" + }, + "description": "Scegliere il tipo di dati AirVisual che si desidera monitorare.", "title": "Configura AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json new file mode 100644 index 00000000000..62a9919e5f3 --- /dev/null +++ b/homeassistant/components/airvisual/translations/nl.json @@ -0,0 +1,29 @@ +{ + "config": { + "error": { + "general_error": "Er is een onbekende fout opgetreden." + }, + "step": { + "geography": { + "data": { + "api_key": "API-sleutel", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad" + }, + "title": "Configureer een geografie" + }, + "node_pro": { + "data": { + "ip_address": "IP adres/hostname van unit", + "password": "Wachtwoord van unit" + } + }, + "user": { + "data": { + "cloud_api": "Geografische ligging", + "type": "Integratietype" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index a528c98e04c..652c4fe1d76 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -16,7 +16,7 @@ "longitude": "Lengdegrad" }, "description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en geografisk plassering.", - "title": "Konfigurer en geografi" + "title": "Konfigurer en Geography" }, "node_pro": { "data": { @@ -32,7 +32,7 @@ "cloud_api": "Geografisk plassering", "latitude": "Breddegrad", "longitude": "Lengdegrad", - "node_pro": "AirVisual Node Pro", + "node_pro": "", "type": "Integrasjonstype" }, "description": "Velg hvilken type AirVisual-data du vil overv\u00e5ke.", diff --git a/homeassistant/components/alarm_control_panel/translations/no.json b/homeassistant/components/alarm_control_panel/translations/no.json index d26d7d0b181..465dd250086 100644 --- a/homeassistant/components/alarm_control_panel/translations/no.json +++ b/homeassistant/components/alarm_control_panel/translations/no.json @@ -8,9 +8,9 @@ "trigger": "Utl\u00f8ser {entity_name}" }, "condition_type": { - "is_armed_away": "{entity_name} aktivert borte", - "is_armed_home": "{entity_name} aktivert hjemme", - "is_armed_night": "{entity_name} aktivert natt", + "is_armed_away": "{entity_name} er aktivert borte", + "is_armed_home": "{entity_name} er aktivert hjemme", + "is_armed_night": "{entity_name} er aktivert natt", "is_disarmed": "{entity_name} er deaktivert", "is_triggered": "{entity_name} er utl\u00f8st" }, diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index d27b903452d..9ec4e8853b9 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 add-on: {addon}?", - "title": "Almond via Hass.io add-on" + "title": "" }, "pick_implementation": { "title": "Velg autentiseringsmetode" diff --git a/homeassistant/components/arcam_fmj/translations/no.json b/homeassistant/components/arcam_fmj/translations/no.json index b78b8cbaa7b..d8a4c453015 100644 --- a/homeassistant/components/arcam_fmj/translations/no.json +++ b/homeassistant/components/arcam_fmj/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Arcam FMJ" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/it.json b/homeassistant/components/atag/translations/it.json new file mode 100644 index 00000000000..190da0f14d7 --- /dev/null +++ b/homeassistant/components/atag/translations/it.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\u00c8 possibile aggiungere un solo dispositivo Atag ad Home Assistant" + }, + "error": { + "connection_error": "Impossibile connettersi, si prega di riprovare" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Porta (10000)" + }, + "title": "Connettersi al dispositivo" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/no.json b/homeassistant/components/atag/translations/no.json index 4b4a5346558..38d35d23943 100644 --- a/homeassistant/components/atag/translations/no.json +++ b/homeassistant/components/atag/translations/no.json @@ -10,7 +10,7 @@ "user": { "data": { "host": "Vert", - "port": "Port (10000)" + "port": "" }, "title": "Koble til enheten" } diff --git a/homeassistant/components/binary_sensor/translations/no.json b/homeassistant/components/binary_sensor/translations/no.json index 1264e770ce4..80ac73264dd 100644 --- a/homeassistant/components/binary_sensor/translations/no.json +++ b/homeassistant/components/binary_sensor/translations/no.json @@ -109,8 +109,8 @@ "on": "V\u00e5t" }, "problem": { - "off": "OK", - "on": "Problem" + "off": "", + "on": "" }, "safety": { "off": "Sikker", diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json new file mode 100644 index 00000000000..b5e59d830f8 --- /dev/null +++ b/homeassistant/components/braviatv/translations/nl.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Deze tv is al geconfigureerd." + }, + "error": { + "cannot_connect": "Geen verbinding, ongeldige host of PIN-code.", + "invalid_host": "Ongeldige hostnaam of IP-adres.", + "unsupported_model": "Uw tv-model wordt niet ondersteund." + }, + "step": { + "authorize": { + "data": { + "pin": "PIN-code" + }, + "description": "Voer de pincode in die wordt weergegeven op de Sony Bravia tv. \n\nAls de pincode niet wordt weergegeven, moet u de Home Assistant op uw tv afmelden, ga naar: Instellingen -> Netwerk -> Instellingen extern apparaat -> Afmelden extern apparaat.", + "title": "Autoriseer Sony Bravia tv" + }, + "user": { + "data": { + "host": "Hostnaam of IP-adres van tv" + }, + "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." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index afd0f0d758c..45644446a3b 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -21,7 +21,7 @@ "host": "TV-vertsnavn eller IP-adresse" }, "description": "Konfigurer Sony Bravia TV-integrasjon. Hvis du har problemer med konfigurasjonen, g\u00e5 til: https://www.home-assistant.io/integrations/braviatv \n\n Forsikre deg om at TV-en er sl\u00e5tt p\u00e5.", - "title": "Sony Bravia TV" + "title": "" } } }, diff --git a/homeassistant/components/brother/translations/no.json b/homeassistant/components/brother/translations/no.json index c612e5f8986..0235c4d1693 100644 --- a/homeassistant/components/brother/translations/no.json +++ b/homeassistant/components/brother/translations/no.json @@ -23,8 +23,8 @@ "data": { "type": "Type skriver" }, - "description": "Vil du legge til Brother-skriveren {Model} med serienummeret {serial_number} til Home Assistant?", - "title": "Oppdaget Brother-Skriveren" + "description": "Vil du legge til Brother-skriveren {model} med serienummeret `{serial_number}` til Home Assistant?", + "title": "Oppdaget Brother Skriver" } } } diff --git a/homeassistant/components/camera/translations/it.json b/homeassistant/components/camera/translations/it.json index 79fe9916de3..a5f4be7967e 100644 --- a/homeassistant/components/camera/translations/it.json +++ b/homeassistant/components/camera/translations/it.json @@ -3,7 +3,7 @@ "_": { "idle": "Inattiva", "recording": "In registrazione", - "streaming": "Streaming" + "streaming": "In trasmissione" } }, "title": "Telecamera" diff --git a/homeassistant/components/camera/translations/no.json b/homeassistant/components/camera/translations/no.json index 6c2dc281761..9960881c81e 100644 --- a/homeassistant/components/camera/translations/no.json +++ b/homeassistant/components/camera/translations/no.json @@ -1,6 +1,7 @@ { "state": { "_": { + "idle": "Inaktiv", "recording": "Opptak", "streaming": "Str\u00f8mming" } diff --git a/homeassistant/components/climate/translations/no.json b/homeassistant/components/climate/translations/no.json index 4e9722bb207..4ac58d07bbb 100644 --- a/homeassistant/components/climate/translations/no.json +++ b/homeassistant/components/climate/translations/no.json @@ -1,27 +1,28 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "Endre HVAC-modus p\u00e5 {entity_name}", - "set_preset_mode": "Endre forh\u00e5ndsinnstilling p\u00e5 {entity_name}" + "set_hvac_mode": "Endre klima-modus p\u00e5 {entity_name}", + "set_preset_mode": "Endre modus p\u00e5 {entity_name}" }, "condition_type": { - "is_hvac_mode": "{entity_name} er satt til en spesifikk HVAC-modus", + "is_hvac_mode": "{entity_name} er satt til en spesifikk klima-modus", "is_preset_mode": "{entity_name} er satt til en spesifikk forh\u00e5ndsinnstilt modus" }, "trigger_type": { "current_humidity_changed": "{entity_name} m\u00e5lt fuktighet er endret", "current_temperature_changed": "{entity_name} m\u00e5lt temperatur er endret", - "hvac_mode_changed": "{entity_name} HVAC-modus er endret" + "hvac_mode_changed": "{entity_name} klima-modus er endret" } }, "state": { "_": { - "auto": "Auto", + "auto": "", "cool": "Kj\u00f8le", "dry": "T\u00f8rr", "fan_only": "Kun vifte", "heat": "Varme", - "heat_cool": "Varme/kj\u00f8lig" + "heat_cool": "Varme/kj\u00f8lig", + "off": "Av" } }, "title": "Klima" diff --git a/homeassistant/components/cover/translations/it.json b/homeassistant/components/cover/translations/it.json index 70589da242c..95f2e34d8eb 100644 --- a/homeassistant/components/cover/translations/it.json +++ b/homeassistant/components/cover/translations/it.json @@ -34,5 +34,5 @@ "stopped": "Arrestato" } }, - "title": "Chiusure" + "title": "Scuri" } \ No newline at end of file diff --git a/homeassistant/components/cover/translations/no.json b/homeassistant/components/cover/translations/no.json index 4d898ec75f8..0aab609ea63 100644 --- a/homeassistant/components/cover/translations/no.json +++ b/homeassistant/components/cover/translations/no.json @@ -9,20 +9,20 @@ "set_tilt_position": "Angi {entity_name} tilt posisjon" }, "condition_type": { - "is_closed": "{entity_name} er stengt", - "is_closing": "{entity_name} stenges", + "is_closed": "{entity_name} er lukket", + "is_closing": "{entity_name} lukker", "is_open": "{entity_name} er \u00e5pen", - "is_opening": "{entity_name} \u00e5pnes", - "is_position": "{entity_name}-posisjonen er", - "is_tilt_position": "{entity_name} vippeposisjon er" + "is_opening": "{entity_name} \u00e5pner", + "is_position": "N\u00e5v\u00e6rende {entity_name} posisjon er", + "is_tilt_position": "N\u00e5v\u00e6rende {entity_name} tilt posisjon er" }, "trigger_type": { "closed": "{entity_name} lukket", - "closing": "{entity_name} lukkes", + "closing": "{entity_name} lukker", "opened": "{entity_name} \u00e5pnet", - "opening": "{entity_name} \u00e5pning", + "opening": "{entity_name} \u00e5pner", "position": "{entity_name} posisjon endringer", - "tilt_position": "{entity_name} endringer i vippeposisjon" + "tilt_position": "{entity_name} tilt posisjon endringer" } }, "state": { diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index d9d64070c88..8b0caa869f8 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -48,6 +48,7 @@ "device_automation": { "trigger_subtype": { "both_buttons": "Beide knoppen", + "bottom_buttons": "Onderste knoppen", "button_1": "Eerste knop", "button_2": "Tweede knop", "button_3": "Derde knop", @@ -64,6 +65,7 @@ "side_4": "Zijde 4", "side_5": "Zijde 5", "side_6": "Zijde 6", + "top_buttons": "Bovenste knoppen", "turn_off": "Uitschakelen", "turn_on": "Inschakelen" }, diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index c743774c41b..e1587a07957 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -11,7 +11,7 @@ "error": { "no_key": "Kunne ikke f\u00e5 en API-n\u00f8kkel" }, - "flow_title": "deCONZ Zigbee gateway ({host})", + "flow_title": "", "step": { "hassio_confirm": { "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegget {addon} ?", @@ -33,7 +33,7 @@ "manual_input": { "data": { "host": "Vert", - "port": "Port" + "port": "" }, "title": "Konfigurer deCONZ gateway" }, @@ -59,12 +59,12 @@ "left": "Venstre", "open": "\u00c5pen", "right": "H\u00f8yre", - "side_1": "Side 1", - "side_2": "Side 2", - "side_3": "Side 3", - "side_4": "Side 4", - "side_5": "Side 5", - "side_6": "Side 6", + "side_1": "", + "side_2": "", + "side_3": "", + "side_4": "", + "side_5": "", + "side_6": "", "top_buttons": "\u00d8verste knappene", "turn_off": "Skru av", "turn_on": "Sl\u00e5 p\u00e5" diff --git a/homeassistant/components/demo/translations/no.json b/homeassistant/components/demo/translations/no.json index ed813c9e505..e85f5b067a0 100644 --- a/homeassistant/components/demo/translations/no.json +++ b/homeassistant/components/demo/translations/no.json @@ -16,5 +16,5 @@ } } }, - "title": "Demo" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/device_tracker/translations/no.json b/homeassistant/components/device_tracker/translations/no.json index 8073a7f5871..56ef663e6c6 100644 --- a/homeassistant/components/device_tracker/translations/no.json +++ b/homeassistant/components/device_tracker/translations/no.json @@ -5,5 +5,11 @@ "is_not_home": "{entity_name} er ikke hjemme" } }, + "state": { + "_": { + "home": "Hjemme", + "not_home": "Borte" + } + }, "title": "Enhetssporing" } \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json new file mode 100644 index 00000000000..36723f36dc0 --- /dev/null +++ b/homeassistant/components/doorbird/translations/nl.json @@ -0,0 +1,14 @@ +{ + "config": { + "flow_title": "DoorBird {name} ({host})", + "step": { + "user": { + "data": { + "name": "Apparaatnaam", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/no.json b/homeassistant/components/elgato/translations/no.json index 2d60155cbb8..34a9bfed772 100644 --- a/homeassistant/components/elgato/translations/no.json +++ b/homeassistant/components/elgato/translations/no.json @@ -7,7 +7,7 @@ "error": { "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." }, - "flow_title": "Elgato Key Light: {serial_number}", + "flow_title": "", "step": { "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json new file mode 100644 index 00000000000..3f643bc67e7 --- /dev/null +++ b/homeassistant/components/elkm1/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord (alleen beveiligd).", + "protocol": "Protocol", + "username": "Gebruikersnaam (alleen beveiligd)." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/no.json b/homeassistant/components/esphome/translations/no.json index c04f0c2a09d..37e3b881b8b 100644 --- a/homeassistant/components/esphome/translations/no.json +++ b/homeassistant/components/esphome/translations/no.json @@ -8,7 +8,7 @@ "invalid_password": "Ugyldig passord!", "resolve_error": "Kan ikke l\u00f8se adressen til ESP. Hvis denne feilen vedvarer, m\u00e5 du [angi en statisk IP-adresse](https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)" }, - "flow_title": "ESPHome: {name}", + "flow_title": "", "step": { "authenticate": { "data": { diff --git a/homeassistant/components/fan/translations/no.json b/homeassistant/components/fan/translations/no.json index 094ca1bc378..c4c425c0eb8 100644 --- a/homeassistant/components/fan/translations/no.json +++ b/homeassistant/components/fan/translations/no.json @@ -13,5 +13,11 @@ "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } }, + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, "title": "Vifte" } \ No newline at end of file diff --git a/homeassistant/components/flume/translations/nl.json b/homeassistant/components/flume/translations/nl.json index fe67b5734d1..830bdc84e55 100644 --- a/homeassistant/components/flume/translations/nl.json +++ b/homeassistant/components/flume/translations/nl.json @@ -11,6 +11,7 @@ "step": { "user": { "data": { + "client_secret": "Client Secret", "password": "Wachtwoord", "username": "Gebruikersnaam" }, diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json new file mode 100644 index 00000000000..3a71a79d137 --- /dev/null +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + }, + "error": { + "general_error": "Er is een onbekende fout opgetreden." + }, + "step": { + "user": { + "data": { + "latitude": "Breedtegraad", + "longitude": "Lengtegraad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json new file mode 100644 index 00000000000..de0f1e0e918 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/it.json @@ -0,0 +1,32 @@ +{ + "config": { + "abort": { + "already_configured": "Questo AVM FRITZ!Box \u00e8 gi\u00e0 configurato.", + "already_in_progress": "La configurazione di AVM FRITZ!Box \u00e8 gi\u00e0 in corso.", + "not_found": "Nessun AVM FRITZ!Box supportato trovato sulla rete." + }, + "error": { + "auth_failed": "Nome utente e/o password non sono corretti." + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "description": "Vuoi impostare {name}?", + "title": "AVM FRITZ!Box" + }, + "user": { + "data": { + "host": "Host o indirizzo IP", + "password": "Password", + "username": "Nome utente" + }, + "description": "Inserisci le informazioni del tuo AVM FRITZ!Box .", + "title": "AVM FRITZ!Box" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index c523c7412d9..b6a7fe0641f 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -8,7 +8,7 @@ "error": { "auth_failed": "Brukernavn og/eller passord er feil." }, - "flow_title": "AVM FRITZ!Box: {name}", + "flow_title": "", "step": { "confirm": { "data": { @@ -16,7 +16,7 @@ "username": "Brukernavn" }, "description": "Vil du sette opp {name} ?", - "title": "AVM FRITZ!Box" + "title": "" }, "user": { "data": { @@ -25,7 +25,7 @@ "username": "Brukernavn" }, "description": "Skriv inn AVM FRITZ!Box informasjonen.", - "title": "AVM FRITZ!Box" + "title": "" } } } diff --git a/homeassistant/components/geonetnz_quakes/translations/no.json b/homeassistant/components/geonetnz_quakes/translations/no.json index b14e0ded378..fc3b339d807 100644 --- a/homeassistant/components/geonetnz_quakes/translations/no.json +++ b/homeassistant/components/geonetnz_quakes/translations/no.json @@ -6,8 +6,8 @@ "step": { "user": { "data": { - "mmi": "MMI", - "radius": "Radius" + "mmi": "", + "radius": "" }, "title": "Fyll ut filterdetaljene." } diff --git a/homeassistant/components/geonetnz_volcano/translations/no.json b/homeassistant/components/geonetnz_volcano/translations/no.json index 17ce4a32b40..36d7216dcd9 100644 --- a/homeassistant/components/geonetnz_volcano/translations/no.json +++ b/homeassistant/components/geonetnz_volcano/translations/no.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "radius": "Radius" + "radius": "" }, "title": "Fyll ut filterdetaljene." } diff --git a/homeassistant/components/gios/translations/no.json b/homeassistant/components/gios/translations/no.json index 93c78b33db9..4b9183d9d87 100644 --- a/homeassistant/components/gios/translations/no.json +++ b/homeassistant/components/gios/translations/no.json @@ -15,7 +15,7 @@ "station_id": "ID til m\u00e5lestasjon" }, "description": "Sett opp GIO\u015a (Polish Chief Inspectorate Of Environmental Protection) luftkvalitet integrering. Hvis du trenger hjelp med konfigurasjonen ta en titt her: https://www.home-assistant.io/integrations/gios", - "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" + "title": "" } } } diff --git a/homeassistant/components/group/translations/no.json b/homeassistant/components/group/translations/no.json index b41c930ac9f..7067eaed946 100644 --- a/homeassistant/components/group/translations/no.json +++ b/homeassistant/components/group/translations/no.json @@ -1,3 +1,11 @@ { + "state": { + "_": { + "off": "Av", + "ok": "", + "on": "P\u00e5", + "problem": "" + } + }, "title": "Gruppe" } \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/no.json b/homeassistant/components/harmony/translations/no.json index 9cae0663208..871b3161fcf 100644 --- a/homeassistant/components/harmony/translations/no.json +++ b/homeassistant/components/harmony/translations/no.json @@ -7,7 +7,7 @@ "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", "unknown": "Uventet feil" }, - "flow_title": "Logitech Harmony Hub {name}", + "flow_title": "", "step": { "link": { "description": "Vil du konfigurere {name} ({host})?", diff --git a/homeassistant/components/hassio/translations/no.json b/homeassistant/components/hassio/translations/no.json index 981cb51c83a..d8a4c453015 100644 --- a/homeassistant/components/hassio/translations/no.json +++ b/homeassistant/components/hassio/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Hass.io" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/no.json b/homeassistant/components/hisense_aehw4a1/translations/no.json index 65c9968dc1e..0b0bf55d7af 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/no.json +++ b/homeassistant/components/hisense_aehw4a1/translations/no.json @@ -7,7 +7,7 @@ "step": { "confirm": { "description": "Vil du konfigurere Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "title": "" } } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 346bc8af9ac..493809dca07 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -33,8 +33,18 @@ "button_2": "Tweede knop", "button_3": "Derde knop", "button_4": "Vierde knop", + "dim_up": "Dim omhoog", + "double_buttons_1_3": "Eerste en derde knop", + "double_buttons_2_4": "Tweede en vierde knop", "turn_off": "Uitschakelen", "turn_on": "Inschakelen" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" knop losgelaten na lang drukken", + "remote_button_short_press": "\"{subtype}\" knop ingedrukt", + "remote_button_short_release": "\"{subtype}\" knop losgelaten", + "remote_double_button_long_press": "Beide \"{subtype}\" vrijgegeven na lang indrukken", + "remote_double_button_short_press": "Beide \"{subtype}\" vrijgegeven" } } } \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/no.json b/homeassistant/components/input_boolean/translations/no.json index b6ffe9f30ef..ed4eac6654f 100644 --- a/homeassistant/components/input_boolean/translations/no.json +++ b/homeassistant/components/input_boolean/translations/no.json @@ -1,3 +1,9 @@ { + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, "title": "Valgt boolsk" } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index d6fbeb4cbbd..e775d869615 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -1,3 +1,12 @@ { - "title": "Internet Printing Protocol (IPP)" + "config": { + "abort": { + "already_configured": "Deze printer is al geconfigureerd.", + "connection_error": "Kan geen verbinding maken met de printer.", + "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.", + "ipp_version_error": "IPP-versie wordt niet ondersteund door printer.", + "parse_error": "Ongeldige reactie van de printer." + } + } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/no.json b/homeassistant/components/ipp/translations/no.json index 2d4fe3e64b4..10ab960272b 100644 --- a/homeassistant/components/ipp/translations/no.json +++ b/homeassistant/components/ipp/translations/no.json @@ -18,7 +18,7 @@ "data": { "base_path": "Relativ bane til skriveren", "host": "Vert eller IP-adresse", - "port": "Port", + "port": "", "ssl": "Skriveren st\u00f8tter kommunikasjon over SSL/TLS", "verify_ssl": "Skriveren bruker et riktig SSL-sertifikat" }, diff --git a/homeassistant/components/islamic_prayer_times/translations/it.json b/homeassistant/components/islamic_prayer_times/translations/it.json new file mode 100644 index 00000000000..cd5155101eb --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/it.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\u00c8 necessaria solo una singola istanza." + }, + "step": { + "user": { + "description": "Vuoi impostare i tempi di preghiera islamici?", + "title": "Impostare i tempi di preghiera islamici" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calc_method": "Metodo di calcolo della preghiera" + } + } + } + }, + "title": "Tempi di preghiera islamica" +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index c513a9a0d7b..45351cd0966 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -68,6 +68,7 @@ }, "options_switch": { "data": { + "more_states": "Aanvullende statussen voor deze zone configureren", "name": "Naam (optioneel)" }, "title": "Schakelbare uitgang configureren" diff --git a/homeassistant/components/light/translations/nl.json b/homeassistant/components/light/translations/nl.json index 761dd2bdc00..5c4dc969b0f 100644 --- a/homeassistant/components/light/translations/nl.json +++ b/homeassistant/components/light/translations/nl.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "flash": "Flash {entity_name}", "toggle": "Omschakelen {entity_name}", "turn_off": "{entity_name} uitschakelen", "turn_on": "{entity_name} inschakelen" diff --git a/homeassistant/components/light/translations/no.json b/homeassistant/components/light/translations/no.json index 8b9d94d18b1..b9bd83ba190 100644 --- a/homeassistant/components/light/translations/no.json +++ b/homeassistant/components/light/translations/no.json @@ -3,7 +3,7 @@ "action_type": { "brightness_decrease": "Reduser lysstyrken p\u00e5 {entity_name}", "brightness_increase": "\u00d8k lysstyrken p\u00e5 {entity_name}", - "flash": "Flash {entity_name}", + "flash": "", "toggle": "Veksle {entity_name}", "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" @@ -17,5 +17,11 @@ "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } }, + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, "title": "Lys" } \ No newline at end of file diff --git a/homeassistant/components/linky/translations/no.json b/homeassistant/components/linky/translations/no.json index b2c565e13df..7ba7479859f 100644 --- a/homeassistant/components/linky/translations/no.json +++ b/homeassistant/components/linky/translations/no.json @@ -16,7 +16,7 @@ "username": "E-post" }, "description": "Skriv inn legitimasjonen din", - "title": "Linky" + "title": "" } } } diff --git a/homeassistant/components/lovelace/translations/no.json b/homeassistant/components/lovelace/translations/no.json index 2fc0c81a46c..d8a4c453015 100644 --- a/homeassistant/components/lovelace/translations/no.json +++ b/homeassistant/components/lovelace/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Lovelace" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/no.json b/homeassistant/components/lutron_caseta/translations/no.json index 970d722fe4c..d8a4c453015 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Lutron Cas\u00e9ta" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/no.json b/homeassistant/components/media_player/translations/no.json index 6358aa9b85a..691ec894a7b 100644 --- a/homeassistant/components/media_player/translations/no.json +++ b/homeassistant/components/media_player/translations/no.json @@ -10,7 +10,12 @@ }, "state": { "_": { - "playing": "Spiller" + "idle": "Inaktiv", + "off": "Av", + "on": "P\u00e5", + "paused": "Pauset", + "playing": "Spiller", + "standby": "Avventer" } }, "title": "Mediaspiller" diff --git a/homeassistant/components/met/translations/no.json b/homeassistant/components/met/translations/no.json index a46b53faf8c..90489288b62 100644 --- a/homeassistant/components/met/translations/no.json +++ b/homeassistant/components/met/translations/no.json @@ -11,7 +11,7 @@ "longitude": "Lengdegrad", "name": "Navn" }, - "description": "Meteorologisk institutt", + "description": "", "title": "Lokasjon" } } diff --git a/homeassistant/components/monoprice/translations/nl.json b/homeassistant/components/monoprice/translations/nl.json new file mode 100644 index 00000000000..8b8126a63ee --- /dev/null +++ b/homeassistant/components/monoprice/translations/nl.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "port": "Seri\u00eble poort" + }, + "title": "Verbinding maken met het apparaat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Naam van bron #1", + "source_2": "Naam van bron #2", + "source_3": "Naam van bron #3", + "source_4": "Naam van bron #4", + "source_5": "Naam van bron #5", + "source_6": "Naam van bron #6" + }, + "title": "Configureer bronnen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/no.json b/homeassistant/components/monoprice/translations/no.json index bbbeed0c1fd..b95b9496951 100644 --- a/homeassistant/components/monoprice/translations/no.json +++ b/homeassistant/components/monoprice/translations/no.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "port": "Serial port", + "port": "Seriell port", "source_1": "Navn p\u00e5 kilden #1", "source_2": "Navn p\u00e5 kilden #2", "source_3": "Navn p\u00e5 kilden #3", diff --git a/homeassistant/components/myq/translations/nl.json b/homeassistant/components/myq/translations/nl.json new file mode 100644 index 00000000000..fd6310cce6a --- /dev/null +++ b/homeassistant/components/myq/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "MyQ is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Verbinding maken met de MyQ-gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json new file mode 100644 index 00000000000..8767b93d173 --- /dev/null +++ b/homeassistant/components/nexia/translations/nl.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Verbinding maken met mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/nl.json b/homeassistant/components/nut/translations/nl.json index 2eaad319712..d0ed6c3c573 100644 --- a/homeassistant/components/nut/translations/nl.json +++ b/homeassistant/components/nut/translations/nl.json @@ -4,6 +4,12 @@ "unknown": "Onverwachte fout" }, "step": { + "resources": { + "data": { + "resources": "Bronnen" + }, + "title": "Kies de te controleren bronnen" + }, "ups": { "title": "Kies een UPS om uit te lezen" }, diff --git a/homeassistant/components/nut/translations/no.json b/homeassistant/components/nut/translations/no.json index 5464e034244..6fd749442c3 100644 --- a/homeassistant/components/nut/translations/no.json +++ b/homeassistant/components/nut/translations/no.json @@ -16,7 +16,7 @@ }, "ups": { "data": { - "alias": "Alias", + "alias": "", "resources": "Ressurser" }, "title": "Velg UPS som skal overv\u00e5kes" @@ -25,7 +25,7 @@ "data": { "host": "Vert", "password": "Passord", - "port": "Port", + "port": "", "username": "Brukernavn" }, "title": "Koble til NUT-serveren" diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index 590b9c90e12..532ec589a65 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -3,6 +3,8 @@ "step": { "user": { "data": { + "api_key": "API-sleutel (e-mail)", + "latitude": "Breedtegraad", "longitude": "Lengtegraad" } } diff --git a/homeassistant/components/opentherm_gw/translations/no.json b/homeassistant/components/opentherm_gw/translations/no.json index 07cc2f56ed6..f0ecf0277b2 100644 --- a/homeassistant/components/opentherm_gw/translations/no.json +++ b/homeassistant/components/opentherm_gw/translations/no.json @@ -13,7 +13,7 @@ "id": "", "name": "Navn" }, - "title": "OpenTherm Gateway" + "title": "" } } }, diff --git a/homeassistant/components/panasonic_viera/translations/no.json b/homeassistant/components/panasonic_viera/translations/no.json index b7f228552d9..469c22ec54f 100644 --- a/homeassistant/components/panasonic_viera/translations/no.json +++ b/homeassistant/components/panasonic_viera/translations/no.json @@ -12,7 +12,7 @@ "step": { "pairing": { "data": { - "pin": "PIN" + "pin": "" }, "description": "Skriv inn PIN-koden som vises p\u00e5 TV-en", "title": "Sammenkobling" @@ -27,5 +27,5 @@ } } }, - "title": "Panasonic Viera" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/person/translations/no.json b/homeassistant/components/person/translations/no.json index 10115f789a6..6d380619114 100644 --- a/homeassistant/components/person/translations/no.json +++ b/homeassistant/components/person/translations/no.json @@ -1,3 +1,9 @@ { - "title": "Person" + "state": { + "_": { + "home": "Hjemme", + "not_home": "Borte" + } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/plant/translations/no.json b/homeassistant/components/plant/translations/no.json index 0a08a5eaed4..e82299e36e9 100644 --- a/homeassistant/components/plant/translations/no.json +++ b/homeassistant/components/plant/translations/no.json @@ -1,3 +1,9 @@ { + "state": { + "_": { + "ok": "", + "problem": "" + } + }, "title": "Plantemonitor" } \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/es.json b/homeassistant/components/powerwall/translations/es.json index 2f8ffcf787f..76835e81480 100644 --- a/homeassistant/components/powerwall/translations/es.json +++ b/homeassistant/components/powerwall/translations/es.json @@ -6,7 +6,7 @@ "error": { "cannot_connect": "No se pudo conectar, por favor int\u00e9ntelo de nuevo", "unknown": "Error inesperado", - "wrong_version": "tu powerwall utiliza una versi\u00f3n de software que no es compatible. Considera actualizar o informar de este problema para que pueda resolverse." + "wrong_version": "Tu powerwall utiliza una versi\u00f3n de software que no es compatible. Considera actualizar o informar de este problema para que pueda resolverse." }, "step": { "user": { diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 09d103c6f60..09a3d570801 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Impossibile connettersi, si prega di riprovare", - "unknown": "Errore imprevisto" + "unknown": "Errore imprevisto", + "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Si prega di considerare l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, "step": { "user": { diff --git a/homeassistant/components/ps4/translations/no.json b/homeassistant/components/ps4/translations/no.json index cd9b80175ad..dd900af0c10 100644 --- a/homeassistant/components/ps4/translations/no.json +++ b/homeassistant/components/ps4/translations/no.json @@ -20,10 +20,10 @@ }, "link": { "data": { - "code": "PIN", + "code": "", "ip_address": "IP adresse", "name": "Navn", - "region": "Region" + "region": "" }, "description": "Skriv inn PlayStation 4-informasjonen. For 'PIN', naviger til 'Innstillinger' p\u00e5 PlayStation 4-konsollen. Naviger deretter til 'Mobile App Connection Settings' og velg 'Add Device'. Tast inn PIN-koden som vises. Se [dokumentasjonen] (https://www.home-assistant.io/components/ps4/) for mer informasjon.", "title": "" diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json new file mode 100644 index 00000000000..c489bef5a2c --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Sensornaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/remote/translations/no.json b/homeassistant/components/remote/translations/no.json index ad3dec70b8f..2e65d515e59 100644 --- a/homeassistant/components/remote/translations/no.json +++ b/homeassistant/components/remote/translations/no.json @@ -1,3 +1,9 @@ { + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, "title": "Fjernkontroll" } \ No newline at end of file diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index 0f126e6decd..7f8be78fd25 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -11,14 +11,14 @@ "step": { "ssdp_confirm": { "description": "Vil du sette opp {name} ?", - "title": "Roku" + "title": "" }, "user": { "data": { "host": "Vert eller IP-adresse" }, "description": "Skriv inn Roku-informasjonen din.", - "title": "Roku" + "title": "" } } } diff --git a/homeassistant/components/scene/translations/no.json b/homeassistant/components/scene/translations/no.json index 827c0c81f38..d8a4c453015 100644 --- a/homeassistant/components/scene/translations/no.json +++ b/homeassistant/components/scene/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Scene" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/script/translations/no.json b/homeassistant/components/script/translations/no.json index caeaf751b81..28122450085 100644 --- a/homeassistant/components/script/translations/no.json +++ b/homeassistant/components/script/translations/no.json @@ -1,3 +1,9 @@ { - "title": "Script" + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.it.json b/homeassistant/components/season/translations/sensor.it.json index e584633325d..d66fb3f09af 100644 --- a/homeassistant/components/season/translations/sensor.it.json +++ b/homeassistant/components/season/translations/sensor.it.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "Autunno", + "spring": "Primavera", + "summer": "Estate", + "winter": "Inverno" + }, "season__season__": { "autumn": "Autunno", "spring": "Primavera", diff --git a/homeassistant/components/sensor/translations/no.json b/homeassistant/components/sensor/translations/no.json index e09aa8dead0..80b6822607a 100644 --- a/homeassistant/components/sensor/translations/no.json +++ b/homeassistant/components/sensor/translations/no.json @@ -23,5 +23,11 @@ "value": "{entity_name} verdi endringer" } }, - "title": "Sensor" + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/ca.json b/homeassistant/components/simplisafe/translations/ca.json index 32ff418a7bd..82cedbbca9f 100644 --- a/homeassistant/components/simplisafe/translations/ca.json +++ b/homeassistant/components/simplisafe/translations/ca.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Codi (utilitzat a la UI de Home Assistant)", "password": "Contrasenya", "username": "Correu electr\u00f2nic" }, @@ -21,7 +22,7 @@ "step": { "init": { "data": { - "code": "Codi (per la UI de Home Assistant)" + "code": "Codi (utilitzat a la UI de Home Assistant)" }, "title": "Configuraci\u00f3 de SimpliSafe" } diff --git a/homeassistant/components/simplisafe/translations/es.json b/homeassistant/components/simplisafe/translations/es.json index 8ffd687b228..4034eac5741 100644 --- a/homeassistant/components/simplisafe/translations/es.json +++ b/homeassistant/components/simplisafe/translations/es.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "C\u00f3digo (utilizado en el interfaz de usuario de Home Assistant)", "password": "Contrase\u00f1a", "username": "Direcci\u00f3n de correo electr\u00f3nico" }, diff --git a/homeassistant/components/simplisafe/translations/it.json b/homeassistant/components/simplisafe/translations/it.json index c30d967d012..c63894ceaf2 100644 --- a/homeassistant/components/simplisafe/translations/it.json +++ b/homeassistant/components/simplisafe/translations/it.json @@ -10,10 +10,11 @@ "step": { "user": { "data": { + "code": "Codice (utilizzato nell'Interfaccia Utente di Home Assistant)", "password": "Password", "username": "Indirizzo E-mail" }, - "title": "Inserisci i tuoi dati" + "title": "Inserisci le tue informazioni." } } }, diff --git a/homeassistant/components/simplisafe/translations/lb.json b/homeassistant/components/simplisafe/translations/lb.json index 8e460289ef3..0f1962c529c 100644 --- a/homeassistant/components/simplisafe/translations/lb.json +++ b/homeassistant/components/simplisafe/translations/lb.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Code (benotzt am Home Assistant Benotzer Interface)", "password": "Passwuert", "username": "E-Mail Adress" }, diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index 0aeb9cb1d95..f585a9c9231 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "code": "Code (gebruikt in Home Assistant)", "password": "Wachtwoord", "username": "E-mailadres" }, diff --git a/homeassistant/components/simplisafe/translations/no.json b/homeassistant/components/simplisafe/translations/no.json index 8fcc86ffb82..582fe57efd9 100644 --- a/homeassistant/components/simplisafe/translations/no.json +++ b/homeassistant/components/simplisafe/translations/no.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Kode (brukt i Home Assistant grensesnitt)", "password": "Passord", "username": "E-postadresse" }, diff --git a/homeassistant/components/simplisafe/translations/ru.json b/homeassistant/components/simplisafe/translations/ru.json index 4f0b9bf4ee3..26665617b1d 100644 --- a/homeassistant/components/simplisafe/translations/ru.json +++ b/homeassistant/components/simplisafe/translations/ru.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "\u041a\u043e\u0434 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 Home Assistant)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b" }, diff --git a/homeassistant/components/simplisafe/translations/zh-Hant.json b/homeassistant/components/simplisafe/translations/zh-Hant.json index 975c863d95d..391362d70c6 100644 --- a/homeassistant/components/simplisafe/translations/zh-Hant.json +++ b/homeassistant/components/simplisafe/translations/zh-Hant.json @@ -10,10 +10,11 @@ "step": { "user": { "data": { + "code": "\u9a57\u8b49\u78bc\uff08\u4f7f\u7528\u65bc Home Assistant UI\uff09", "password": "\u5bc6\u78bc", "username": "\u96fb\u5b50\u90f5\u4ef6\u5730\u5740" }, - "title": "\u586b\u5beb\u8cc7\u8a0a" + "title": "\u586b\u5beb\u8cc7\u8a0a\u3002" } } }, diff --git a/homeassistant/components/smartthings/translations/nl.json b/homeassistant/components/smartthings/translations/nl.json index b7e40798e83..85a0ed4a736 100644 --- a/homeassistant/components/smartthings/translations/nl.json +++ b/homeassistant/components/smartthings/translations/nl.json @@ -8,6 +8,15 @@ "webhook_error": "SmartThings kon het in 'base_url` geconfigureerde endpoint niet goedkeuren. Lees de componentvereisten door." }, "step": { + "authorize": { + "title": "Machtig Home Assistant" + }, + "pat": { + "data": { + "access_token": "Toegangstoken" + }, + "title": "Persoonlijk toegangstoken invoeren" + }, "select_location": { "data": { "location_id": "Locatie" diff --git a/homeassistant/components/starline/translations/no.json b/homeassistant/components/starline/translations/no.json index e17bd9ad95e..a370aa271f5 100644 --- a/homeassistant/components/starline/translations/no.json +++ b/homeassistant/components/starline/translations/no.json @@ -18,8 +18,8 @@ "data": { "captcha_code": "Kode fra bilde" }, - "description": "{captcha_img}", - "title": "Captcha" + "description": "", + "title": "" }, "auth_mfa": { "data": { diff --git a/homeassistant/components/switch/translations/no.json b/homeassistant/components/switch/translations/no.json index dc57fa94203..a8ed6128773 100644 --- a/homeassistant/components/switch/translations/no.json +++ b/homeassistant/components/switch/translations/no.json @@ -14,5 +14,11 @@ "turned_on": "{entity_name} sl\u00e5tt p\u00e5" } }, + "state": { + "_": { + "off": "Av", + "on": "P\u00e5" + } + }, "title": "Bryter" } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index e7ce33ec43c..9a85aa8d2d5 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -4,9 +4,11 @@ "already_configured": "Host gi\u00e0 configurato" }, "error": { + "connection": "Errore di connessione: controlla host, porta e SSL", "login": "Errore di accesso: si prega di controllare il nome utente e la password", "missing_data": "Dati mancanti: si prega di riprovare pi\u00f9 tardi o un'altra configurazione", - "otp_failed": "Autenticazione in due fasi fallita, riprovare con un nuovo codice di accesso" + "otp_failed": "Autenticazione in due fasi fallita, riprovare con un nuovo codice di accesso", + "unknown": "Errore sconosciuto: si prega di controllare i registri per ottenere maggiori dettagli" }, "flow_title": "Synology DSM {name} ({host})", "step": { diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index bda3e337dda..5e2fa85fb52 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -3,12 +3,19 @@ "abort": { "already_configured": "Host is al geconfigureerd." }, + "error": { + "connection": "Verbindingsfout: controleer uw host, poort & ssl", + "login": "Aanmeldingsfout: controleer uw gebruikersnaam en wachtwoord", + "otp_failed": "Tweestapsverificatie is mislukt, probeer het opnieuw met een nieuwe toegangscode", + "unknown": "Onbekende fout: controleer de logs voor meer informatie" + }, "flow_title": "Synology DSM {name} ({host})", "step": { "2sa": { "data": { "otp_code": "Code" - } + }, + "title": "Synology DSM: tweestapsverificatie" }, "link": { "data": { diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index d9e24638f72..7b2301768db 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -10,7 +10,7 @@ "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode", "unknown": "Ukjent feil: sjekk loggene for \u00e5 f\u00e5 flere detaljer" }, - "flow_title": "Synology DSM {name} ({host})", + "flow_title": "", "step": { "2sa": { "data": { @@ -27,7 +27,7 @@ "username": "Brukernavn" }, "description": "Vil du konfigurere {name} ({host})?", - "title": "Synology DSM" + "title": "" }, "user": { "data": { @@ -38,7 +38,7 @@ "ssl": "Bruk SSL/TLS til \u00e5 koble til NAS-en", "username": "Brukernavn" }, - "title": "Synology DSM" + "title": "" } } } diff --git a/homeassistant/components/tado/translations/nl.json b/homeassistant/components/tado/translations/nl.json new file mode 100644 index 00000000000..5099637a637 --- /dev/null +++ b/homeassistant/components/tado/translations/nl.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "fallback": "Schakel de terugvalmodus in." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/timer/translations/no.json b/homeassistant/components/timer/translations/no.json new file mode 100644 index 00000000000..431e4895c1a --- /dev/null +++ b/homeassistant/components/timer/translations/no.json @@ -0,0 +1,9 @@ +{ + "state": { + "_": { + "active": "Aktiv", + "idle": "Inaktiv", + "paused": "Pauset" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json new file mode 100644 index 00000000000..3196a58675c --- /dev/null +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Account al geconfigureerd" + }, + "error": { + "login": "Aanmeldingsfout: controleer uw gebruikersnaam en wachtwoord" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index f93f2cc4748..c312f98f3d2 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -12,7 +12,7 @@ "password": "Passord", "username": "Brukernavn" }, - "title": "Total Connect" + "title": "" } } } diff --git a/homeassistant/components/twentemilieu/translations/no.json b/homeassistant/components/twentemilieu/translations/no.json index 84fe87eda04..d7d383ae371 100644 --- a/homeassistant/components/twentemilieu/translations/no.json +++ b/homeassistant/components/twentemilieu/translations/no.json @@ -15,7 +15,7 @@ "post_code": "Postnummer" }, "description": "Sett opp Twente Milieu som gir informasjon om innsamling av avfall p\u00e5 adressen din.", - "title": "Twente Milieu" + "title": "" } } } diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index e55ae8fd493..62611d0aac8 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -32,6 +32,7 @@ "device_tracker": { "data": { "detection_time": "Tijd in seconden vanaf laatst gezien tot beschouwd als weg", + "ignore_wired_bug": "Schakel UniFi bedrade buglogica uit", "track_clients": "Volg netwerkclients", "track_devices": "Netwerkapparaten volgen (Ubiquiti-apparaten)", "track_wired_clients": "Inclusief bedrade netwerkcli\u00ebnten" diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index f1979b7fb97..2ecdb20d4a9 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -46,6 +46,12 @@ "description": "Konfigurere enhetssporing", "title": "UniFi-alternativ 1/3" }, + "init": { + "data": { + "one": "Tom", + "other": "Tomme" + } + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "B\u00e5ndbreddebrukssensorer for nettverksklienter" diff --git a/homeassistant/components/upnp/translations/it.json b/homeassistant/components/upnp/translations/it.json index 06c40aa95a7..5a411a97aa9 100644 --- a/homeassistant/components/upnp/translations/it.json +++ b/homeassistant/components/upnp/translations/it.json @@ -26,7 +26,7 @@ "enable_sensors": "Aggiungi sensori di traffico", "igd": "UPnP/IGD" }, - "title": "Opzioni di configurazione per UPnP/IGD" + "title": "Opzioni di configurazione" } } } diff --git a/homeassistant/components/vacuum/translations/no.json b/homeassistant/components/vacuum/translations/no.json index dbf94d1243b..8e62bc64e81 100644 --- a/homeassistant/components/vacuum/translations/no.json +++ b/homeassistant/components/vacuum/translations/no.json @@ -1,12 +1,12 @@ { "device_automation": { "action_type": { - "clean": "La {entity_name} rengj\u00f8res", - "dock": "La {entity_name} tilbake til dock" + "clean": "La {entity_name} rengj\u00f8re", + "dock": "La {entity_name} returnere til dokken" }, "condition_type": { - "is_cleaning": "{entity_name} rengj\u00f8res", - "is_docked": "{entity_name} er docked" + "is_cleaning": "{entity_name} rengj\u00f8r", + "is_docked": "{entity_name} er dokket" }, "trigger_type": { "cleaning": "{entity_name} startet rengj\u00f8ringen", @@ -18,6 +18,10 @@ "cleaning": "Rengj\u00f8ring", "docked": "Dokket", "error": "Feil", + "idle": "Inaktiv", + "off": "Av", + "on": "P\u00e5", + "paused": "Pauset", "returning": "Returner til dokken" } }, diff --git a/homeassistant/components/vera/translations/nl.json b/homeassistant/components/vera/translations/nl.json new file mode 100644 index 00000000000..ff260e9bbb0 --- /dev/null +++ b/homeassistant/components/vera/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Er is al een controller geconfigureerd." + }, + "step": { + "user": { + "data": { + "vera_controller_url": "Controller-URL" + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Vera controller opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/no.json b/homeassistant/components/vizio/translations/no.json index 16a8c6c392e..ea2b68d75fc 100644 --- a/homeassistant/components/vizio/translations/no.json +++ b/homeassistant/components/vizio/translations/no.json @@ -30,7 +30,7 @@ "data": { "access_token": "Tilgangstoken", "device_class": "Enhetstype", - "host": ":", + "host": ":", "name": "Navn" }, "description": "En tilgangstoken er bare n\u00f8dvendig for TV-er. Hvis du konfigurerer en TV og ikke har tilgangstoken enn\u00e5, m\u00e5 du la den st\u00e5 tom for \u00e5 g\u00e5 gjennom en sammenkoblingsprosess.", diff --git a/homeassistant/components/weather/translations/it.json b/homeassistant/components/weather/translations/it.json index 2345dc16eb3..b6559782581 100644 --- a/homeassistant/components/weather/translations/it.json +++ b/homeassistant/components/weather/translations/it.json @@ -15,7 +15,7 @@ "snowy-rainy": "Nevoso, piovoso", "sunny": "Soleggiato", "windy": "Ventoso", - "windy-variant": "Ventoso" + "windy-variant": "Preval. Ventoso" } } } \ No newline at end of file diff --git a/homeassistant/components/wemo/translations/no.json b/homeassistant/components/wemo/translations/no.json index 1aecf403a4c..13476f63ec2 100644 --- a/homeassistant/components/wemo/translations/no.json +++ b/homeassistant/components/wemo/translations/no.json @@ -7,7 +7,7 @@ "step": { "confirm": { "description": "\u00d8nsker du \u00e5 sette opp Wemo?", - "title": "Wemo" + "title": "" } } } diff --git a/homeassistant/components/wled/translations/no.json b/homeassistant/components/wled/translations/no.json index 34c645e9802..e0c165b352c 100644 --- a/homeassistant/components/wled/translations/no.json +++ b/homeassistant/components/wled/translations/no.json @@ -7,7 +7,7 @@ "error": { "connection_error": "Kunne ikke koble til WLED-enheten." }, - "flow_title": "WLED: {name}", + "flow_title": "", "step": { "user": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 1562bbb6526..758376f4926 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -1,29 +1,29 @@ { - "config": { - "step": { - "user": { - "title": "Xiaomi Miio", - "description": "Select to which device you want to connect.", - "data": { - "gateway": "Connect to a Xiaomi Gateway" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "connect_error": "Failed to connect, please try again", + "no_device_selected": "No device selected, please select one device." + }, + "step": { + "gateway": { + "data": { + "host": "IP adress", + "name": "Name of the Gateway", + "token": "API Token" + }, + "description": "You will need the API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions.", + "title": "Connect to a Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Connect to a Xiaomi Gateway" + }, + "description": "Select to which device you want to connect.", + "title": "Xiaomi Miio" + } } - }, - "gateway": { - "title": "Connect to a Xiaomi Gateway", - "description": "You will need the API Token, see https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instructions.", - "data": { - "host": "IP adress", - "token": "API Token", - "name": "Name of the Gateway" - } - } - }, - "error": { - "connect_error": "Failed to connect, please try again", - "no_device_selected": "No device selected, please select one device." - }, - "abort": { - "already_configured": "Device is already configured" } - } -} +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/it.json b/homeassistant/components/zwave/translations/it.json index e8e3b78a25e..3ea2dc94d87 100644 --- a/homeassistant/components/zwave/translations/it.json +++ b/homeassistant/components/zwave/translations/it.json @@ -23,11 +23,11 @@ "dead": "Disattivo", "initializing": "Avvio", "ready": "Pronto", - "sleeping": "In attesa" + "sleeping": "Dormiente" }, "query_stage": { - "dead": "Disattivo ({query_stage})", - "initializing": "Avvio ({query_stage})" + "dead": "Disattivo", + "initializing": "Avvio" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/nl.json b/homeassistant/components/zwave/translations/nl.json index dc3513a3c71..96e697c021a 100644 --- a/homeassistant/components/zwave/translations/nl.json +++ b/homeassistant/components/zwave/translations/nl.json @@ -26,8 +26,8 @@ "sleeping": "Slaapt" }, "query_stage": { - "dead": "Onbereikbaar ({query_stage})", - "initializing": "Initialiseren ( {query_stage} )" + "dead": "Onbereikbaar", + "initializing": "Initialiseren" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/no.json b/homeassistant/components/zwave/translations/no.json index 1a214262feb..d2a40ec5d80 100644 --- a/homeassistant/components/zwave/translations/no.json +++ b/homeassistant/components/zwave/translations/no.json @@ -24,6 +24,10 @@ "initializing": "Initialiserer", "ready": "Klar", "sleeping": "Sover" + }, + "query_stage": { + "dead": "D\u00f8d", + "initializing": "Initialiserer" } } } \ No newline at end of file From 79c3148fc039bda1acd68033e9e8b690d7436af5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 29 Apr 2020 02:18:15 +0200 Subject: [PATCH 117/511] Fix async call in sync context in steam_online (#34823) --- homeassistant/components/steam_online/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/steam_online/sensor.py b/homeassistant/components/steam_online/sensor.py index d25ebb7221b..97109decae2 100644 --- a/homeassistant/components/steam_online/sensor.py +++ b/homeassistant/components/steam_online/sensor.py @@ -11,7 +11,7 @@ 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 async_track_time_interval +from homeassistant.helpers.event import track_time_interval from homeassistant.util.dt import utc_from_timestamp _LOGGER = logging.getLogger(__name__) @@ -65,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): entities[entity_next].async_schedule_update_ha_state(True) entity_next = (entity_next + 1) % len(entities) - async_track_time_interval(hass, do_update, BASE_INTERVAL) + track_time_interval(hass, do_update, BASE_INTERVAL) class SteamSensor(Entity): From 3debb7cdf3b22932f33ef9e707b1c28214822b37 Mon Sep 17 00:00:00 2001 From: Brendon Go Date: Tue, 28 Apr 2020 17:25:32 -0700 Subject: [PATCH 118/511] Fix typo in arest sensor (#34833) --- homeassistant/components/arest/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/arest/sensor.py b/homeassistant/components/arest/sensor.py index 638c0e2557a..7e50b1df8ff 100644 --- a/homeassistant/components/arest/sensor.py +++ b/homeassistant/components/arest/sensor.py @@ -205,7 +205,7 @@ class ArestData: try: if str(self._pin[0]) == "A": response = requests.get( - f"{self._resource,}/analog/{self._pin[1:]}", timeout=10 + f"{self._resource}/analog/{self._pin[1:]}", timeout=10 ) self.data = {"value": response.json()["return_value"]} except TypeError: From c695b6f6a5a9bdd69b2a0c25a62a9d3f4c3751ee Mon Sep 17 00:00:00 2001 From: Brendon Go Date: Tue, 28 Apr 2020 17:27:03 -0700 Subject: [PATCH 119/511] Remove unnecessary space in greeneye_monitor unique_id (#34834) --- homeassistant/components/greeneye_monitor/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py index 06a60ab3fd7..42f7c0334b3 100644 --- a/homeassistant/components/greeneye_monitor/sensor.py +++ b/homeassistant/components/greeneye_monitor/sensor.py @@ -108,7 +108,7 @@ class GEMSensor(Entity): @property def unique_id(self): """Return a unique ID for this sensor.""" - return f"{self._monitor_serial_number}-{self._sensor_type }-{self._number}" + return f"{self._monitor_serial_number}-{self._sensor_type}-{self._number}" @property def name(self): From c82ece27212f5b445412587cdd19605e82ec0a12 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Apr 2020 20:35:04 -0500 Subject: [PATCH 120/511] Fix pylint CI (#34836) --- azure-pipelines-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 49b032ed2e8..da60db941da 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -211,6 +211,9 @@ stages: pip install -U pip setuptools wheel pip install -r requirements_all.txt -c homeassistant/package_constraints.txt pip install -r requirements_test.txt -c homeassistant/package_constraints.txt + # This is a TEMP. Eventually we should make sure our 4 dependencies drop typing. + # Find offending deps with `pipdeptree -r -p typing` + pip uninstall -y typing - script: | . venv/bin/activate pip install -e . From 592f316b3ffdc65ada8625150e5315399eac7031 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 28 Apr 2020 23:22:33 -0500 Subject: [PATCH 121/511] Fix race condition in august test under py38 (#34775) --- tests/components/august/mocks.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 62249e0fb1e..39b18411d66 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -109,15 +109,19 @@ async def _create_august_with_devices( def lock_return_activities_side_effect(access_token, device_id): lock = _get_device_detail("locks", device_id) return [ - _mock_lock_operation_activity(lock, "lock"), - _mock_door_operation_activity(lock, "doorclosed"), + _mock_lock_operation_activity(lock, "lock", 0), + # There is a check to prevent out of order events + # so we set the doorclosed event in the future + # to prevent a race condition where we reject the event + # because it happened before the dooropen event. + _mock_door_operation_activity(lock, "doorclosed", 2000), ] def unlock_return_activities_side_effect(access_token, device_id): lock = _get_device_detail("locks", device_id) return [ - _mock_lock_operation_activity(lock, "unlock"), - _mock_door_operation_activity(lock, "dooropen"), + _mock_lock_operation_activity(lock, "unlock", 0), + _mock_door_operation_activity(lock, "dooropen", 0), ] if "get_lock_detail" not in api_call_side_effects: @@ -288,10 +292,10 @@ async def _mock_doorsense_missing_august_lock_detail(hass): return await _mock_lock_from_fixture(hass, "get_lock.online_missing_doorsense.json") -def _mock_lock_operation_activity(lock, action): +def _mock_lock_operation_activity(lock, action, offset): return LockOperationActivity( { - "dateTime": time.time() * 1000, + "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, @@ -299,10 +303,10 @@ def _mock_lock_operation_activity(lock, action): ) -def _mock_door_operation_activity(lock, action): +def _mock_door_operation_activity(lock, action, offset): return DoorOperationActivity( { - "dateTime": time.time() * 1000, + "dateTime": (time.time() + offset) * 1000, "deviceID": lock.device_id, "deviceType": "lock", "action": action, From f64c35c3f977c9d62726d4154472154c54332bda Mon Sep 17 00:00:00 2001 From: escoand Date: Wed, 29 Apr 2020 07:17:35 +0200 Subject: [PATCH 122/511] Handle more fritzbox edge cases (#34802) --- .../components/fritzbox/config_flow.py | 11 +++-- .../components/fritzbox/strings.json | 3 +- tests/components/fritzbox/test_config_flow.py | 41 +++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/fritzbox/config_flow.py b/homeassistant/components/fritzbox/config_flow.py index b4265aa01fe..25a81333bd6 100644 --- a/homeassistant/components/fritzbox/config_flow.py +++ b/homeassistant/components/fritzbox/config_flow.py @@ -2,6 +2,7 @@ from urllib.parse import urlparse from pyfritzhome import Fritzhome, LoginError +from requests.exceptions import HTTPError import voluptuous as vol from homeassistant import config_entries @@ -32,6 +33,7 @@ DATA_SCHEMA_CONFIRM = vol.Schema( RESULT_AUTH_FAILED = "auth_failed" RESULT_NOT_FOUND = "not_found" +RESULT_NOT_SUPPORTED = "not_supported" RESULT_SUCCESS = "success" @@ -67,12 +69,15 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) try: fritzbox.login() + fritzbox.get_device_elements() fritzbox.logout() return RESULT_SUCCESS - except OSError: - return RESULT_NOT_FOUND except LoginError: return RESULT_AUTH_FAILED + except HTTPError: + return RESULT_NOT_SUPPORTED + except OSError: + return RESULT_NOT_FOUND async def async_step_import(self, user_input=None): """Handle configuration by yaml file.""" @@ -129,7 +134,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="already_configured") self._host = host - self._name = user_input[ATTR_UPNP_FRIENDLY_NAME] + self._name = user_input.get(ATTR_UPNP_FRIENDLY_NAME) or host self.context["title_placeholders"] = {"name": self._name} return await self.async_step_confirm() diff --git a/homeassistant/components/fritzbox/strings.json b/homeassistant/components/fritzbox/strings.json index 4cb5b7bcdcc..227aeedf84d 100644 --- a/homeassistant/components/fritzbox/strings.json +++ b/homeassistant/components/fritzbox/strings.json @@ -21,7 +21,8 @@ "abort": { "already_in_progress": "AVM FRITZ!Box configuration is already in progress.", "already_configured": "This AVM FRITZ!Box is already configured.", - "not_found": "No supported AVM FRITZ!Box found on the network." + "not_found": "No supported AVM FRITZ!Box found on the network.", + "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." }, "error": { "auth_failed": "Username and/or password are incorrect." diff --git a/tests/components/fritzbox/test_config_flow.py b/tests/components/fritzbox/test_config_flow.py index c73578de646..8bfd992347f 100644 --- a/tests/components/fritzbox/test_config_flow.py +++ b/tests/components/fritzbox/test_config_flow.py @@ -4,6 +4,7 @@ from unittest.mock import Mock, patch from pyfritzhome import LoginError import pytest +from requests.exceptions import HTTPError from homeassistant.components.fritzbox.const import DOMAIN from homeassistant.components.ssdp import ( @@ -121,6 +122,28 @@ async def test_ssdp(hass: HomeAssistantType, fritz: Mock): assert result["result"].unique_id == "only-a-test" +async def test_ssdp_no_friendly_name(hass: HomeAssistantType, fritz: Mock): + """Test starting a flow from discovery without friendly name.""" + MOCK_NO_NAME = MOCK_SSDP_DATA.copy() + del MOCK_NO_NAME[ATTR_UPNP_FRIENDLY_NAME] + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "ssdp"}, data=MOCK_NO_NAME + ) + assert result["type"] == "form" + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: "fake_pass", CONF_USERNAME: "fake_user"}, + ) + assert result["type"] == "create_entry" + assert result["title"] == "fake_host" + assert result["data"][CONF_HOST] == "fake_host" + assert result["data"][CONF_PASSWORD] == "fake_pass" + assert result["data"][CONF_USERNAME] == "fake_user" + assert result["result"].unique_id == "only-a-test" + + async def test_ssdp_auth_failed(hass: HomeAssistantType, fritz: Mock): """Test starting a flow from discovery with authentication failure.""" fritz().login.side_effect = LoginError("Boom") @@ -159,6 +182,24 @@ async def test_ssdp_not_successful(hass: HomeAssistantType, fritz: Mock): assert result["reason"] == "not_found" +async def test_ssdp_not_supported(hass: HomeAssistantType, fritz: Mock): + """Test starting a flow from discovery with unsupported device.""" + fritz().get_device_elements.side_effect = HTTPError("Boom") + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "ssdp"}, data=MOCK_SSDP_DATA + ) + assert result["type"] == "form" + assert result["step_id"] == "confirm" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_PASSWORD: "whatever", CONF_USERNAME: "whatever"}, + ) + assert result["type"] == "abort" + assert result["reason"] == "not_supported" + + async def test_ssdp_already_in_progress_unique_id(hass: HomeAssistantType, fritz: Mock): """Test starting a flow from discovery twice.""" result = await hass.config_entries.flow.async_init( From 976457ccefc069a3aada539f0bcb367b60a356ab Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 29 Apr 2020 07:19:55 +0200 Subject: [PATCH 123/511] UniFi - Add a second roaming event (#34819) --- homeassistant/components/unifi/unifi_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/unifi/unifi_client.py b/homeassistant/components/unifi/unifi_client.py index c9bd038dd77..398df4206b6 100644 --- a/homeassistant/components/unifi/unifi_client.py +++ b/homeassistant/components/unifi/unifi_client.py @@ -25,10 +25,12 @@ LOGGER = logging.getLogger(__name__) CLIENT_BLOCKED = (WIRED_CLIENT_BLOCKED, WIRELESS_CLIENT_BLOCKED) CLIENT_UNBLOCKED = (WIRED_CLIENT_UNBLOCKED, WIRELESS_CLIENT_UNBLOCKED) WIRED_CLIENT = (WIRED_CLIENT_CONNECTED, WIRED_CLIENT_DISCONNECTED) +WIRELESS_CLIENT_ROAMRADIO = "EVT_WU_RoamRadio" WIRELESS_CLIENT = ( WIRELESS_CLIENT_CONNECTED, WIRELESS_CLIENT_DISCONNECTED, WIRELESS_CLIENT_ROAM, + WIRELESS_CLIENT_ROAMRADIO, ) @@ -72,6 +74,7 @@ class UniFiClient(UniFiBase): self.wireless_connection = self.client.event.event in ( WIRELESS_CLIENT_CONNECTED, WIRELESS_CLIENT_ROAM, + WIRELESS_CLIENT_ROAMRADIO, ) elif self.client.event.event in WIRED_CLIENT: From de062bc2a6f986679f7230e51ec8b0cb449be0a5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 29 Apr 2020 07:20:06 +0200 Subject: [PATCH 124/511] Updated frontend to 20200427.1 (#34831) --- 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 f75f164aca5..9759c38af7d 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200427.0"], + "requirements": ["home-assistant-frontend==20200427.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 2d3ae822273..938427eadd4 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.1 -home-assistant-frontend==20200427.0 +home-assistant-frontend==20200427.1 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 5aa9301a6f1..97c6e2802eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -713,7 +713,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.0 +home-assistant-frontend==20200427.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ffc8cf7eb6d..6762171da9b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -291,7 +291,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.0 +home-assistant-frontend==20200427.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From c56391e9728cc7f0f08d63c7a05fa1a942758211 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 29 Apr 2020 00:20:54 -0500 Subject: [PATCH 125/511] Remove legacy discovery for roku (#34794) --- homeassistant/components/discovery/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 64816acaaf3..89d1fcc615b 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -37,7 +37,6 @@ SERVICE_KONNECTED = "konnected" SERVICE_MOBILE_APP = "hass_mobile_app" SERVICE_NETGEAR = "netgear_router" SERVICE_OCTOPRINT = "octoprint" -SERVICE_ROKU = "roku" SERVICE_SABNZBD = "sabnzbd" SERVICE_SAMSUNG_PRINTER = "samsung_printer" SERVICE_TELLDUSLIVE = "tellstick" @@ -59,7 +58,6 @@ SERVICE_HANDLERS = { SERVICE_HASSIO: ("hassio", None), SERVICE_APPLE_TV: ("apple_tv", None), SERVICE_ENIGMA2: ("media_player", "enigma2"), - SERVICE_ROKU: ("roku", None), SERVICE_WINK: ("wink", None), SERVICE_XIAOMI_GW: ("xiaomi_aqara", None), SERVICE_SABNZBD: ("sabnzbd", None), From 5a93a6eec4b49a995715f02491d7857447ffd16b Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 29 Apr 2020 00:21:10 -0500 Subject: [PATCH 126/511] Remove legacy discovery for directv (#34793) * remove legacy discovery for directv * Update __init__.py * Update __init__.py --- homeassistant/components/discovery/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 89d1fcc615b..b9b3f51f60d 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -69,7 +69,6 @@ SERVICE_HANDLERS = { "panasonic_viera": ("media_player", "panasonic_viera"), "yamaha": ("media_player", "yamaha"), "logitech_mediaserver": ("media_player", "squeezebox"), - "directv": ("media_player", "directv"), "denonavr": ("media_player", "denonavr"), "frontier_silicon": ("media_player", "frontier_silicon"), "openhome": ("media_player", "openhome"), From 942cd7caa54996d72004192ecadd2b263c06ff10 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 29 Apr 2020 07:21:34 +0200 Subject: [PATCH 127/511] Fix sync call in async context generic_thermostat (#34822) --- homeassistant/components/generic_thermostat/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index 396d347c3c9..d7889513402 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -325,7 +325,7 @@ class GenericThermostat(ClimateEntity, RestoreEntity): _LOGGER.error("Unrecognized hvac mode: %s", hvac_mode) return # Ensure we update the current operation after changing the mode - self.schedule_update_ha_state() + self.async_write_ha_state() async def async_set_temperature(self, **kwargs): """Set new target temperature.""" From 13f4393042021ff47adf23a3cba931ab156ba588 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 00:58:55 -0500 Subject: [PATCH 128/511] Fix flapping reload tests (#34837) --- homeassistant/helpers/entity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 738b49f4c54..b5d36f6a2f5 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -493,13 +493,14 @@ class Entity(ABC): async def async_remove(self) -> None: """Remove entity from Home Assistant.""" assert self.hass is not None - await self.async_internal_will_remove_from_hass() - await self.async_will_remove_from_hass() if self._on_remove is not None: while self._on_remove: self._on_remove.pop()() + await self.async_internal_will_remove_from_hass() + await self.async_will_remove_from_hass() + self.hass.states.async_remove(self.entity_id, context=self._context) async def async_added_to_hass(self) -> None: From 24fd395d0994dfed5b5ac12cab90202f75bcda66 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 29 Apr 2020 08:56:43 +0200 Subject: [PATCH 129/511] Bump version to 0.110.0dev0 (#34827) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2b975f52e18..2579e9a15ff 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 109 +MINOR_VERSION = 110 PATCH_VERSION = "0.dev0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 6e0d61e5e8f7bd77edf60d0c79dd9c1a680cf68f Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Wed, 29 Apr 2020 14:38:20 +0300 Subject: [PATCH 130/511] Fix Islamic prayer times naming (#34784) --- .../components/islamic_prayer_times/const.py | 13 ++++-- .../components/islamic_prayer_times/sensor.py | 4 +- .../islamic_prayer_times/strings.json | 2 +- .../islamic_prayer_times/translations/en.json | 40 +++++++++---------- .../islamic_prayer_times/test_sensor.py | 4 +- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/islamic_prayer_times/const.py b/homeassistant/components/islamic_prayer_times/const.py index 5a9007689d9..ee7512c2d7a 100644 --- a/homeassistant/components/islamic_prayer_times/const.py +++ b/homeassistant/components/islamic_prayer_times/const.py @@ -1,12 +1,19 @@ """Constants for the Islamic Prayer component.""" DOMAIN = "islamic_prayer_times" NAME = "Islamic Prayer Times" -SENSOR_SUFFIX = "Prayer" PRAYER_TIMES_ICON = "mdi:calendar-clock" -SENSOR_TYPES = ["Fajr", "Sunrise", "Dhuhr", "Asr", "Maghrib", "Isha", "Midnight"] +SENSOR_TYPES = { + "Fajr": "prayer", + "Sunrise": "time", + "Dhuhr": "prayer", + "Asr": "prayer", + "Maghrib": "prayer", + "Isha": "prayer", + "Midnight": "time", +} -CONF_CALC_METHOD = "calc_method" +CONF_CALC_METHOD = "calculation_method" CALC_METHODS = ["isna", "karachi", "mwl", "makkah"] DEFAULT_CALC_METHOD = "isna" diff --git a/homeassistant/components/islamic_prayer_times/sensor.py b/homeassistant/components/islamic_prayer_times/sensor.py index d1f4baa90bc..92a0a491d8d 100644 --- a/homeassistant/components/islamic_prayer_times/sensor.py +++ b/homeassistant/components/islamic_prayer_times/sensor.py @@ -5,7 +5,7 @@ from homeassistant.const import DEVICE_CLASS_TIMESTAMP from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_SUFFIX, SENSOR_TYPES +from .const import DATA_UPDATED, DOMAIN, PRAYER_TIMES_ICON, SENSOR_TYPES _LOGGER = logging.getLogger(__name__) @@ -33,7 +33,7 @@ class IslamicPrayerTimeSensor(Entity): @property def name(self): """Return the name of the sensor.""" - return f"{self.sensor_type} {SENSOR_SUFFIX}" + return f"{self.sensor_type} {SENSOR_TYPES[self.sensor_type]}" @property def unique_id(self): diff --git a/homeassistant/components/islamic_prayer_times/strings.json b/homeassistant/components/islamic_prayer_times/strings.json index ebbea482122..857ce4c2dff 100644 --- a/homeassistant/components/islamic_prayer_times/strings.json +++ b/homeassistant/components/islamic_prayer_times/strings.json @@ -15,7 +15,7 @@ "step": { "init": { "data": { - "calc_method": "Prayer calculation method" + "calculation_method": "Prayer calculation method" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/en.json b/homeassistant/components/islamic_prayer_times/translations/en.json index 155a693ab1f..4db928c0ede 100644 --- a/homeassistant/components/islamic_prayer_times/translations/en.json +++ b/homeassistant/components/islamic_prayer_times/translations/en.json @@ -1,23 +1,23 @@ { - "config": { - "abort": { - "one_instance_allowed": "Only a single instance is necessary." - }, - "step": { - "user": { - "description": "Do you want to set up Islamic Prayer Times?", - "title": "Set up Islamic Prayer Times" - } - } + "config": { + "abort": { + "one_instance_allowed": "Only a single instance is necessary." }, - "options": { - "step": { - "init": { - "data": { - "calc_method": "Prayer calculation method" - } - } + "step": { + "user": { + "description": "Do you want to set up Islamic Prayer Times?", + "title": "Set up Islamic Prayer Times" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Prayer calculation method" } - }, - "title": "Islamic Prayer Times" -} \ No newline at end of file + } + } + }, + "title": "Islamic Prayer Times" +} diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 4954287b864..0579664ae7b 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -23,6 +23,8 @@ async def test_islamic_prayer_times_sensors(hass): for prayer in PRAYER_TIMES: assert ( - hass.states.get(f"sensor.{prayer}_prayer").state + hass.states.get( + f"sensor.{prayer}_{islamic_prayer_times.const.SENSOR_TYPES[prayer]}" + ).state == PRAYER_TIMES_TIMESTAMPS[prayer].isoformat() ) From 2788de9b108b9adfe9604b1ffeb0e4769c14b614 Mon Sep 17 00:00:00 2001 From: Quentame Date: Wed, 29 Apr 2020 13:41:37 +0200 Subject: [PATCH 131/511] Bump python-synology to 0.7.3 (#34847) --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index 5834217b6ea..bb3c5ce3aec 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.2"], + "requirements": ["python-synology==0.7.3"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 97c6e2802eb..4bc53309239 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1683,7 +1683,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.2 +python-synology==0.7.3 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6762171da9b..e7607669405 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -650,7 +650,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.2 +python-synology==0.7.3 # homeassistant.components.tado python-tado==0.8.1 From 15569f1e7fc2fa79de88f8c933973e1bd818f517 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Wed, 29 Apr 2020 13:46:27 +0200 Subject: [PATCH 132/511] Fix tuya network failure startup (#34057) * Tuya initialization retry on failure * Rename exception * Changed managed exception * Manage different cases of ConnectionError * Log messages correction * Test always catching exceptions * Update for Lint * Update tuya library to 0.0.6 - Catch new library exception * Tuya initialization retry on failure * Rename exception * Changed managed exception * Manage different cases of ConnectionError * Log messages correction * Test always catching exceptions * Update for Lint * Update tuya library to 0.0.6 - Catch new library exception * Catch wrong credential * Revert "Catch wrong credential" This reverts commit 7fb797de5254bf2fa7811793b066174d9d261428. * Catch wrong credential * Remove trailing whitespace * Black formatting * Import Exception from tuyaapi * Remove str to exception * Force CI checks * Force CI checks * Rebase conflict * Tuya initialization retry on failure * Rename exception * Changed managed exception * Manage different cases of ConnectionError * Log messages correction * Test always catching exceptions * Update for Lint * Update tuya library to 0.0.6 - Catch new library exception * Catch wrong credential * Revert "Catch wrong credential" This reverts commit 7fb797de5254bf2fa7811793b066174d9d261428. * Tuya initialization retry on failure * Rename exception * Changed managed exception * Catch wrong credential * Force CI checks * Force CI checks * Rebase conflict * Rebase * Force Test * Force Test --- homeassistant/components/tuya/__init__.py | 34 +++++++++++++++++++-- homeassistant/components/tuya/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py index 74b1d1439ad..c50e6787d89 100644 --- a/homeassistant/components/tuya/__init__.py +++ b/homeassistant/components/tuya/__init__.py @@ -3,6 +3,7 @@ from datetime import timedelta import logging from tuyaha import TuyaApi +from tuyaha.tuyaapi import TuyaAPIException, TuyaNetException, TuyaServerException import voluptuous as vol from homeassistant.const import CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME @@ -11,7 +12,7 @@ from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import track_time_interval +from homeassistant.helpers.event import call_later, track_time_interval _LOGGER = logging.getLogger(__name__) @@ -22,6 +23,9 @@ PARALLEL_UPDATES = 0 DOMAIN = "tuya" DATA_TUYA = "data_tuya" +FIRST_RETRY_TIME = 60 +MAX_RETRY_TIME = 900 + SIGNAL_DELETE_ENTITY = "tuya_delete" SIGNAL_UPDATE_ENTITY = "tuya_update" @@ -52,17 +56,41 @@ CONFIG_SCHEMA = vol.Schema( ) -def setup(hass, config): +def setup(hass, config, retry_delay=FIRST_RETRY_TIME): """Set up Tuya Component.""" + _LOGGER.debug("Setting up integration") + tuya = TuyaApi() username = config[DOMAIN][CONF_USERNAME] password = config[DOMAIN][CONF_PASSWORD] country_code = config[DOMAIN][CONF_COUNTRYCODE] platform = config[DOMAIN][CONF_PLATFORM] + try: + tuya.init(username, password, country_code, platform) + except (TuyaNetException, TuyaServerException): + + _LOGGER.warning( + "Connection error during integration setup. Will retry in %s seconds", + retry_delay, + ) + + def retry_setup(now): + """Retry setup if a error happens on tuya API.""" + setup(hass, config, retry_delay=min(2 * retry_delay, MAX_RETRY_TIME)) + + call_later(hass, retry_delay, retry_setup) + + return True + + except TuyaAPIException as exc: + _LOGGER.error( + "Connection error during integration setup. Error: %s", exc, + ) + return False + hass.data[DATA_TUYA] = tuya - tuya.init(username, password, country_code, platform) hass.data[DOMAIN] = {"entities": {}} def load_devices(device_list): diff --git a/homeassistant/components/tuya/manifest.json b/homeassistant/components/tuya/manifest.json index cd6cb333020..1279f0a2f66 100644 --- a/homeassistant/components/tuya/manifest.json +++ b/homeassistant/components/tuya/manifest.json @@ -2,6 +2,6 @@ "domain": "tuya", "name": "Tuya", "documentation": "https://www.home-assistant.io/integrations/tuya", - "requirements": ["tuyaha==0.0.5"], + "requirements": ["tuyaha==0.0.6"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index 4bc53309239..c8c136d1021 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2062,7 +2062,7 @@ tp-connected==0.0.4 transmissionrpc==0.11 # homeassistant.components.tuya -tuyaha==0.0.5 +tuyaha==0.0.6 # homeassistant.components.twentemilieu twentemilieu==0.3.0 From 58bff0a183401e3cddbf4583756037333a0e91a5 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 29 Apr 2020 16:27:45 +0200 Subject: [PATCH 133/511] Reload braviatv entry after options update (#34576) * Reload entry after options update * Undo update listener when unloading --- homeassistant/components/braviatv/__init__.py | 17 +++++++++++++++-- .../components/braviatv/config_flow.py | 3 ++- homeassistant/components/braviatv/const.py | 2 ++ .../components/braviatv/media_player.py | 3 ++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/braviatv/__init__.py b/homeassistant/components/braviatv/__init__.py index 9c55ef01cee..46fd8675358 100644 --- a/homeassistant/components/braviatv/__init__.py +++ b/homeassistant/components/braviatv/__init__.py @@ -5,7 +5,7 @@ from bravia_tv import BraviaRC from homeassistant.const import CONF_HOST, CONF_MAC -from .const import DOMAIN +from .const import BRAVIARC, DOMAIN, UNDO_UPDATE_LISTENER PLATFORMS = ["media_player"] @@ -20,8 +20,13 @@ async def async_setup_entry(hass, config_entry): host = config_entry.data[CONF_HOST] mac = config_entry.data[CONF_MAC] + undo_listener = config_entry.add_update_listener(update_listener) + hass.data.setdefault(DOMAIN, {}) - hass.data[DOMAIN][config_entry.entry_id] = BraviaRC(host, mac) + hass.data[DOMAIN][config_entry.entry_id] = { + BRAVIARC: BraviaRC(host, mac), + UNDO_UPDATE_LISTENER: undo_listener, + } for component in PLATFORMS: hass.async_create_task( @@ -41,7 +46,15 @@ async def async_unload_entry(hass, config_entry): ] ) ) + + hass.data[DOMAIN][config_entry.entry_id][UNDO_UPDATE_LISTENER]() + if unload_ok: hass.data[DOMAIN].pop(config_entry.entry_id) return unload_ok + + +async def update_listener(hass, config_entry): + """Handle options update.""" + await hass.config_entries.async_reload(config_entry.entry_id) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index be2a91c8429..660e2e83ea1 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -15,6 +15,7 @@ from .const import ( # pylint:disable=unused-import ATTR_CID, ATTR_MAC, ATTR_MODEL, + BRAVIARC, CLIENTID_PREFIX, CONF_IGNORED_SOURCES, DOMAIN, @@ -152,7 +153,7 @@ class BraviaTVOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): """Manage the options.""" - self.braviarc = self.hass.data[DOMAIN][self.config_entry.entry_id] + self.braviarc = self.hass.data[DOMAIN][self.config_entry.entry_id][BRAVIARC] if not self.braviarc.is_connected(): await self.hass.async_add_executor_job( self.braviarc.connect, self.pin, CLIENTID_PREFIX, NICKNAME, diff --git a/homeassistant/components/braviatv/const.py b/homeassistant/components/braviatv/const.py index 1fa96e6a98d..a5d7a88d4c3 100644 --- a/homeassistant/components/braviatv/const.py +++ b/homeassistant/components/braviatv/const.py @@ -6,8 +6,10 @@ ATTR_MODEL = "model" CONF_IGNORED_SOURCES = "ignored_sources" +BRAVIARC = "braviarc" BRAVIA_CONFIG_FILE = "bravia.conf" CLIENTID_PREFIX = "HomeAssistant" DEFAULT_NAME = f"{ATTR_MANUFACTURER} Bravia TV" DOMAIN = "braviatv" NICKNAME = "Home Assistant" +UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index 49ae466f23c..eb75542460f 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -30,6 +30,7 @@ from homeassistant.util.json import load_json from .const import ( ATTR_MANUFACTURER, BRAVIA_CONFIG_FILE, + BRAVIARC, CLIENTID_PREFIX, CONF_IGNORED_SOURCES, DEFAULT_NAME, @@ -103,7 +104,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): "model": config_entry.title, } - braviarc = hass.data[DOMAIN][config_entry.entry_id] + braviarc = hass.data[DOMAIN][config_entry.entry_id][BRAVIARC] ignored_sources = config_entry.options.get(CONF_IGNORED_SOURCES, []) From a6ee2995bcb4e373b9488d7345becd0462ae16e8 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 29 Apr 2020 18:06:25 +0200 Subject: [PATCH 134/511] Fix CVE-2020-1967 (#34853) --- build.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.json b/build.json index 331999b5470..b2c3cedc378 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:7.1.0", - "armhf": "homeassistant/armhf-homeassistant-base:7.1.0", - "armv7": "homeassistant/armv7-homeassistant-base:7.1.0", - "amd64": "homeassistant/amd64-homeassistant-base:7.1.0", - "i386": "homeassistant/i386-homeassistant-base:7.1.0" + "aarch64": "homeassistant/aarch64-homeassistant-base:7.2.0", + "armhf": "homeassistant/armhf-homeassistant-base:7.2.0", + "armv7": "homeassistant/armv7-homeassistant-base:7.2.0", + "amd64": "homeassistant/amd64-homeassistant-base:7.2.0", + "i386": "homeassistant/i386-homeassistant-base:7.2.0" }, "labels": { "io.hass.type": "core" From f656f352a3ea8622aabec088a7f0fca042e43833 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 11:34:45 -0500 Subject: [PATCH 135/511] Remove side effects from rachio switch init (#34799) * Remove side effects from rachio switch init * Remove useless inits --- homeassistant/components/rachio/switch.py | 69 ++++++----------------- 1 file changed, 16 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index f1f2cb26687..95f01d31518 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -88,14 +88,10 @@ def _create_entities(hass, config_entry): class RachioSwitch(RachioDevice, SwitchEntity): """Represent a Rachio state that can be toggled.""" - def __init__(self, controller, poll=True): + def __init__(self, controller): """Initialize a new Rachio switch.""" super().__init__(controller) - - if poll: - self._state = self._poll_update() - else: - self._state = None + self._state = None @property def name(self) -> str: @@ -107,10 +103,6 @@ class RachioSwitch(RachioDevice, SwitchEntity): """Return whether the switch is currently on.""" return self._state - @abstractmethod - def _poll_update(self, data=None) -> bool: - """Poll the API.""" - @callback def _async_handle_any_update(self, *args, **kwargs) -> None: """Determine whether an update event applies to this device.""" @@ -129,11 +121,6 @@ class RachioSwitch(RachioDevice, SwitchEntity): class RachioStandbySwitch(RachioSwitch): """Representation of a standby status/button.""" - def __init__(self, controller): - """Instantiate a new Rachio standby mode switch.""" - super().__init__(controller, poll=True) - self._poll_update(controller.init_data) - @property def name(self) -> str: """Return the name of the standby switch.""" @@ -149,13 +136,6 @@ class RachioStandbySwitch(RachioSwitch): """Return an icon for the standby switch.""" return "mdi:power" - def _poll_update(self, data=None) -> bool: - """Request the state from the API.""" - if data is None: - data = self._controller.rachio.device.get(self._controller.controller_id)[1] - - return not data[KEY_ON] - @callback def _async_handle_update(self, *args, **kwargs) -> None: """Update the state using webhook data.""" @@ -176,6 +156,9 @@ class RachioStandbySwitch(RachioSwitch): async def async_added_to_hass(self): """Subscribe to updates.""" + if KEY_ON in self._controller.init_data: + self._state = not self._controller.init_data[KEY_ON] + self.async_on_remove( async_dispatcher_connect( self.hass, @@ -188,11 +171,6 @@ class RachioStandbySwitch(RachioSwitch): class RachioRainDelay(RachioSwitch): """Representation of a rain delay status/switch.""" - def __init__(self, controller): - """Instantiate a new Rachio rain delay switch.""" - super().__init__(controller, poll=True) - self._poll_update(controller.init_data) - @property def name(self) -> str: """Return the name of the switch.""" @@ -208,18 +186,6 @@ class RachioRainDelay(RachioSwitch): """Return an icon for rain delay.""" return "mdi:camera-timer" - def _poll_update(self, data=None) -> bool: - """Request the state from the API.""" - # API returns either 0 or current UNIX time when rain delay was canceled - # depending if it was done from the app or via the API - if data is None: - data = self._controller.rachio.device.get(self._controller.controller_id)[1] - - try: - return data[KEY_RAIN_DELAY] / 1000 > as_timestamp(now()) - except KeyError: - return False - @callback def _async_handle_update(self, *args, **kwargs) -> None: """Update the state using webhook data.""" @@ -242,6 +208,11 @@ class RachioRainDelay(RachioSwitch): async def async_added_to_hass(self): """Subscribe to updates.""" + if KEY_RAIN_DELAY in self._controller.init_data: + self._state = self._controller.init_data[ + KEY_RAIN_DELAY + ] / 1000 > as_timestamp(now()) + self.async_on_remove( async_dispatcher_connect( self.hass, @@ -266,8 +237,7 @@ class RachioZone(RachioSwitch): self._zone_type = data.get(KEY_CUSTOM_CROP, {}).get(KEY_NAME) self._summary = "" self._current_schedule = current_schedule - super().__init__(controller, poll=False) - self._state = self.zone_id == self._current_schedule.get(KEY_ZONE_ID) + super().__init__(controller) def __str__(self): """Display the zone as a string.""" @@ -336,11 +306,6 @@ class RachioZone(RachioSwitch): """Stop watering all zones.""" self._controller.stop_watering() - def _poll_update(self, data=None) -> bool: - """Poll the API to check whether the zone is running.""" - self._current_schedule = self._controller.current_schedule - return self.zone_id == self._current_schedule.get(KEY_ZONE_ID) - @callback def _async_handle_update(self, *args, **kwargs) -> None: """Handle incoming webhook zone data.""" @@ -358,6 +323,8 @@ class RachioZone(RachioSwitch): async def async_added_to_hass(self): """Subscribe to updates.""" + self._state = self.zone_id == self._current_schedule.get(KEY_ZONE_ID) + self.async_on_remove( async_dispatcher_connect( self.hass, SIGNAL_RACHIO_ZONE_UPDATE, self._async_handle_update @@ -376,8 +343,7 @@ class RachioSchedule(RachioSwitch): self._schedule_enabled = data[KEY_ENABLED] self._summary = data[KEY_SUMMARY] self._current_schedule = current_schedule - super().__init__(controller, poll=False) - self._state = self._schedule_id == self._current_schedule.get(KEY_SCHEDULE_ID) + super().__init__(controller) @property def name(self) -> str: @@ -420,11 +386,6 @@ class RachioSchedule(RachioSwitch): """Stop watering all zones.""" self._controller.stop_watering() - def _poll_update(self, data=None) -> bool: - """Poll the API to check whether the schedule is running.""" - self._current_schedule = self._controller.current_schedule - return self._schedule_id == self._current_schedule.get(KEY_SCHEDULE_ID) - @callback def _async_handle_update(self, *args, **kwargs) -> None: """Handle incoming webhook schedule data.""" @@ -445,6 +406,8 @@ class RachioSchedule(RachioSwitch): async def async_added_to_hass(self): """Subscribe to updates.""" + self._state = self._schedule_id == self._current_schedule.get(KEY_SCHEDULE_ID) + self.async_on_remove( async_dispatcher_connect( self.hass, SIGNAL_RACHIO_SCHEDULE_UPDATE, self._async_handle_update From 481b2035dbd99dc859f6c0fa9bff71f9e121b2b2 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Wed, 29 Apr 2020 12:39:09 -0400 Subject: [PATCH 136/511] Clean up Rachio binary sensor init (#34855) * Update binary sensor * Move online state to subclass --- .../components/rachio/binary_sensor.py | 33 ++++--------------- homeassistant/components/rachio/const.py | 1 - 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index 4976714f0a2..49f46578f76 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -15,7 +15,6 @@ from .const import ( KEY_STATUS, KEY_SUBTYPE, SIGNAL_RACHIO_CONTROLLER_UPDATE, - STATUS_OFFLINE, STATUS_ONLINE, ) from .entity import RachioDevice @@ -41,13 +40,10 @@ def _create_entities(hass, config_entry): class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity): """Represent a binary sensor that reflects a Rachio state.""" - def __init__(self, controller, poll=True): + def __init__(self, controller): """Set up a new Rachio controller binary sensor.""" super().__init__(controller) - if poll: - self._state = self._poll_update() - else: - self._state = None + self._state = None @property def is_on(self) -> bool: @@ -64,10 +60,6 @@ class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity): # For this device self._async_handle_update(args, kwargs) - @abstractmethod - def _poll_update(self, data=None) -> bool: - """Request the state from the API.""" - @abstractmethod def _async_handle_update(self, *args, **kwargs) -> None: """Handle an update to the state of this sensor.""" @@ -86,11 +78,6 @@ class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity): class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): """Represent a binary sensor that reflects if the controller is online.""" - def __init__(self, controller): - """Set up a new Rachio controller online binary sensor.""" - super().__init__(controller, poll=False) - self._state = self._poll_update(controller.init_data) - @property def name(self) -> str: """Return the name of this sensor including the controller name.""" @@ -111,18 +98,10 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): """Return the name of an icon for this sensor.""" return "mdi:wifi-strength-4" if self.is_on else "mdi:wifi-strength-off-outline" - def _poll_update(self, data=None) -> bool: - """Request the state from the API.""" - if data is None: - data = self._controller.rachio.device.get(self._controller.controller_id)[1] - - if data[KEY_STATUS] == STATUS_ONLINE: - return True - if data[KEY_STATUS] == STATUS_OFFLINE: - return False - _LOGGER.warning( - '"%s" reported in unknown state "%s"', self.name, data[KEY_STATUS] - ) + async def async_added_to_hass(self): + """Get initial state.""" + self._state = self._controller.init_data[KEY_STATUS] == STATUS_ONLINE + await super().async_added_to_hass() @callback def _async_handle_update(self, *args, **kwargs) -> None: diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 99e26f63835..016218906f4 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -53,7 +53,6 @@ RACHIO_API_EXCEPTIONS = ( ) STATUS_ONLINE = "ONLINE" -STATUS_OFFLINE = "OFFLINE" SIGNAL_RACHIO_UPDATE = f"{DOMAIN}_update" SIGNAL_RACHIO_CONTROLLER_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_controller" From b8a5597d3e63c8f5514737d4eabdd3c06895e7a3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 29 Apr 2020 13:29:44 -0600 Subject: [PATCH 137/511] Fix Flu Near You exception re: stale coroutines (#34880) --- .../components/flunearyou/__init__.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py index 1e8e2e20b2d..6e1c8ddb3d2 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -131,15 +131,6 @@ class FluNearYouData: self.latitude = latitude self.longitude = longitude - self._api_coros = { - CATEGORY_CDC_REPORT: self._client.cdc_reports.status_by_coordinates( - latitude, longitude - ), - CATEGORY_USER_REPORT: self._client.user_reports.status_by_coordinates( - latitude, longitude - ), - } - self._api_category_count = { CATEGORY_CDC_REPORT: 0, CATEGORY_USER_REPORT: 0, @@ -155,8 +146,17 @@ class FluNearYouData: if self._api_category_count[api_category] == 0: return + if api_category == CATEGORY_CDC_REPORT: + api_coro = self._client.cdc_reports.status_by_coordinates( + self.latitude, self.longitude + ) + else: + api_coro = self._client.user_reports.status_by_coordinates( + self.latitude, self.longitude + ) + try: - self.data[api_category] = await self._api_coros[api_category] + self.data[api_category] = await api_coro except FluNearYouError as err: LOGGER.error("Unable to get %s data: %s", api_category, err) self.data[api_category] = None @@ -200,7 +200,7 @@ class FluNearYouData: """Update Flu Near You data.""" tasks = [ self._async_get_data_from_api(api_category) - for api_category in self._api_coros + for api_category in self._api_category_count ] await asyncio.gather(*tasks) From 5516063f466b15023e977ef1e25238c30039edd5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 15:44:40 -0500 Subject: [PATCH 138/511] Prevent tplink tests from doing I/O (#34879) --- tests/components/tplink/test_init.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index 85bf0781864..d8e3f76cac3 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -71,6 +71,8 @@ async def test_configuring_device_types(hass, name, cls, platform, count): "homeassistant.components.tplink.common.Discover.discover" ) as discover, patch( "homeassistant.components.tplink.common.SmartDevice._query_helper" + ), patch( + "homeassistant.components.tplink.light.async_setup_entry", return_value=True, ): discovery_data = { f"123.123.123.{c}": cls("123.123.123.123") for c in range(count) From 6ce081928708060ba8d9bf49b8fefdb1ffeebe70 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 16:00:31 -0500 Subject: [PATCH 139/511] Prevent homekit fans from going to 100% than speed when turning on (#34875) --- homeassistant/components/homekit/type_fans.py | 14 +++++++------- tests/components/homekit/test_type_fans.py | 15 +++++++-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 9167c8fcf5d..b7208b1746c 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -94,13 +94,13 @@ class Fan(HomeAccessory): _LOGGER.debug("Fan _set_chars: %s", char_values) if CHAR_ACTIVE in char_values: if char_values[CHAR_ACTIVE]: - is_on = False - state = self.hass.states.get(self.entity_id) - if state and state.state == STATE_ON: - is_on = True - # Only set the state to active if we - # did not get a rotation speed or its off - if not is_on or CHAR_ROTATION_SPEED not in char_values: + # If the device supports set speed we + # do not want to turn on as it will take + # the fan to 100% than to the desired speed. + # + # Setting the speed will take care of turning + # on the fan if SUPPORT_SET_SPEED is set. + if not self.char_speed or CHAR_ROTATION_SPEED not in char_values: self.set_state(1) else: # Its off, nothing more to do as setting the diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 915e7c59d7c..ca6e03217f3 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -419,8 +419,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): ) await hass.async_block_till_done() acc.speed_mapping.speed_to_states.assert_called_with(42) - assert call_turn_on - assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id + assert not call_turn_on assert call_set_speed[0] assert call_set_speed[0].data[ATTR_ENTITY_ID] == entity_id assert call_set_speed[0].data[ATTR_SPEED] == "ludicrous" @@ -430,11 +429,11 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): assert call_set_direction[0] assert call_set_direction[0].data[ATTR_ENTITY_ID] == entity_id assert call_set_direction[0].data[ATTR_DIRECTION] == DIRECTION_REVERSE - assert len(events) == 4 + assert len(events) == 3 - assert events[1].data[ATTR_VALUE] is True - assert events[2].data[ATTR_VALUE] == DIRECTION_REVERSE - assert events[3].data[ATTR_VALUE] == "ludicrous" + assert events[0].data[ATTR_VALUE] is True + assert events[1].data[ATTR_VALUE] == DIRECTION_REVERSE + assert events[2].data[ATTR_VALUE] == "ludicrous" hass.states.async_set( entity_id, @@ -482,7 +481,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): # and we set a fan speed await hass.async_block_till_done() acc.speed_mapping.speed_to_states.assert_called_with(42) - assert len(events) == 7 + assert len(events) == 6 assert call_set_speed[1] assert call_set_speed[1].data[ATTR_ENTITY_ID] == entity_id assert call_set_speed[1].data[ATTR_SPEED] == "ludicrous" @@ -526,7 +525,7 @@ async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): ) await hass.async_block_till_done() - assert len(events) == 8 + assert len(events) == 7 assert call_turn_off assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id assert len(call_set_speed) == 2 From 97c82089b4668a76afcbb639b66bd002de60d645 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 16:02:59 -0500 Subject: [PATCH 140/511] Abort nexia import if the username is already configured (#34863) --- homeassistant/components/nexia/config_flow.py | 3 ++ homeassistant/components/nexia/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/nexia/test_config_flow.py | 41 +++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nexia/config_flow.py b/homeassistant/components/nexia/config_flow.py index c26e42a22d2..d71a5470c98 100644 --- a/homeassistant/components/nexia/config_flow.py +++ b/homeassistant/components/nexia/config_flow.py @@ -88,6 +88,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_import(self, user_input): """Handle import.""" + for entry in self._async_current_entries(): + if entry.data[CONF_USERNAME] == user_input[CONF_USERNAME]: + return self.async_abort(reason="already_configured") return await self.async_step_user(user_input) diff --git a/homeassistant/components/nexia/manifest.json b/homeassistant/components/nexia/manifest.json index f09d4d1a4d1..e86d2072db8 100644 --- a/homeassistant/components/nexia/manifest.json +++ b/homeassistant/components/nexia/manifest.json @@ -1,7 +1,7 @@ { "domain": "nexia", "name": "Nexia", - "requirements": ["nexia==0.9.2"], + "requirements": ["nexia==0.9.3"], "codeowners": ["@ryannazaretian", "@bdraco"], "documentation": "https://www.home-assistant.io/integrations/nexia", "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index c8c136d1021..3a039d18832 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -931,7 +931,7 @@ netdisco==2.6.0 neurio==0.3.1 # homeassistant.components.nexia -nexia==0.9.2 +nexia==0.9.3 # homeassistant.components.nextcloud nextcloudmonitor==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e7607669405..f02da029de7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -366,7 +366,7 @@ nessclient==0.9.15 netdisco==2.6.0 # homeassistant.components.nexia -nexia==0.9.2 +nexia==0.9.3 # homeassistant.components.nsw_fuel_station nsw-fuel-api-client==1.0.10 diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index 3cb57d77f12..ff6f8590287 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -74,3 +74,44 @@ async def test_form_cannot_connect(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_import(hass): + """Test we get the form with import source.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + with patch( + "homeassistant.components.nexia.config_flow.NexiaHome.get_name", + return_value="myhouse", + ), patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + side_effect=MagicMock(), + ), patch( + "homeassistant.components.nexia.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.nexia.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"] == "create_entry" + assert result["title"] == "myhouse" + assert result["data"] == { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_USERNAME: "username", CONF_PASSWORD: "password"}, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "already_configured" From 6ae7f31947d6ef8aaf58fa3ab94cab5a71dbc284 Mon Sep 17 00:00:00 2001 From: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Date: Wed, 29 Apr 2020 16:05:20 -0500 Subject: [PATCH 141/511] SmartThings continue correct config flow after external auth (#34862) --- .../components/smartthings/__init__.py | 10 + .../components/smartthings/config_flow.py | 6 +- .../components/smartthings/smartapp.py | 59 +- .../smartthings/test_config_flow.py | 1028 +++++++++++------ tests/components/smartthings/test_smartapp.py | 62 - 5 files changed, 698 insertions(+), 467 deletions(-) diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 97a7d32a9c1..e4d720c94e5 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -37,6 +37,7 @@ from .const import ( TOKEN_REFRESH_INTERVAL, ) from .smartapp import ( + format_unique_id, setup_smartapp, setup_smartapp_endpoint, smartapp_sync_subscriptions, @@ -76,6 +77,15 @@ async def async_migrate_entry(hass: HomeAssistantType, entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Initialize config entry which represents an installed SmartApp.""" + # For backwards compat + if entry.unique_id is None: + hass.config_entries.async_update_entry( + entry, + unique_id=format_unique_id( + entry.data[CONF_APP_ID], entry.data[CONF_LOCATION_ID] + ), + ) + if not validate_webhook_requirements(hass): _LOGGER.warning( "The 'base_url' of the 'http' integration must be configured and start with 'https://'" diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index cb4623cea1c..c03ade4d8b1 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -7,7 +7,7 @@ from pysmartthings.installedapp import format_install_url import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN +from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN, HTTP_UNAUTHORIZED from homeassistant.helpers.aiohttp_client import async_get_clientsession # pylint: disable=unused-import @@ -26,6 +26,7 @@ from .const import ( from .smartapp import ( create_app, find_app, + format_unique_id, get_webhook_url, setup_smartapp, setup_smartapp_endpoint, @@ -138,7 +139,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) return self._show_step_pat(errors) except ClientResponseError as ex: - if ex.status == 401: + if ex.status == HTTP_UNAUTHORIZED: errors[CONF_ACCESS_TOKEN] = "token_unauthorized" _LOGGER.debug( "Unauthorized error received setting up SmartApp", exc_info=True @@ -183,6 +184,7 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ) self.location_id = user_input[CONF_LOCATION_ID] + await self.async_set_unique_id(format_unique_id(self.app_id, self.location_id)) return await self.async_step_authorize() async def async_step_authorize(self, user_input=None): diff --git a/homeassistant/components/smartthings/smartapp.py b/homeassistant/components/smartthings/smartapp.py index 0b86a430d89..7d02a04d2ff 100644 --- a/homeassistant/components/smartthings/smartapp.py +++ b/homeassistant/components/smartthings/smartapp.py @@ -39,7 +39,6 @@ from .const import ( CONF_CLOUDHOOK_URL, CONF_INSTALLED_APP_ID, CONF_INSTANCE_ID, - CONF_LOCATION_ID, CONF_REFRESH_TOKEN, DATA_BROKERS, DATA_MANAGER, @@ -53,6 +52,11 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +def format_unique_id(app_id: str, location_id: str) -> str: + """Format the unique id for a config entry.""" + return f"{app_id}_{location_id}" + + async def find_app(hass: HomeAssistantType, api): """Find an existing SmartApp for this installation of hass.""" apps = await api.apps() @@ -366,13 +370,20 @@ async def smartapp_sync_subscriptions( _LOGGER.debug("Subscriptions for app '%s' are up-to-date", installed_app_id) -async def smartapp_install(hass: HomeAssistantType, req, resp, app): - """Handle a SmartApp installation and continue the config flow.""" +async def _continue_flow( + hass: HomeAssistantType, + app_id: str, + location_id: str, + installed_app_id: str, + refresh_token: str, +): + """Continue a config flow if one is in progress for the specific installed app.""" + unique_id = format_unique_id(app_id, location_id) flow = next( ( flow for flow in hass.config_entries.flow.async_progress() - if flow["handler"] == DOMAIN + if flow["handler"] == DOMAIN and flow["context"]["unique_id"] == unique_id ), None, ) @@ -380,18 +391,23 @@ async def smartapp_install(hass: HomeAssistantType, req, resp, app): await hass.config_entries.flow.async_configure( flow["flow_id"], { - CONF_INSTALLED_APP_ID: req.installed_app_id, - CONF_LOCATION_ID: req.location_id, - CONF_REFRESH_TOKEN: req.refresh_token, + CONF_INSTALLED_APP_ID: installed_app_id, + CONF_REFRESH_TOKEN: refresh_token, }, ) _LOGGER.debug( "Continued config flow '%s' for SmartApp '%s' under parent app '%s'", flow["flow_id"], - req.installed_app_id, - app.app_id, + installed_app_id, + app_id, ) + +async def smartapp_install(hass: HomeAssistantType, req, resp, app): + """Handle a SmartApp installation and continue the config flow.""" + await _continue_flow( + hass, app.app_id, req.location_id, req.installed_app_id, req.refresh_token + ) _LOGGER.debug( "Installed SmartApp '%s' under parent app '%s'", req.installed_app_id, @@ -420,30 +436,9 @@ async def smartapp_update(hass: HomeAssistantType, req, resp, app): app.app_id, ) - flow = next( - ( - flow - for flow in hass.config_entries.flow.async_progress() - if flow["handler"] == DOMAIN - ), - None, + await _continue_flow( + hass, app.app_id, req.location_id, req.installed_app_id, req.refresh_token ) - if flow is not None: - await hass.config_entries.flow.async_configure( - flow["flow_id"], - { - CONF_INSTALLED_APP_ID: req.installed_app_id, - CONF_LOCATION_ID: req.location_id, - CONF_REFRESH_TOKEN: req.refresh_token, - }, - ) - _LOGGER.debug( - "Continued config flow '%s' for SmartApp '%s' under parent app '%s'", - flow["flow_id"], - req.installed_app_id, - app.app_id, - ) - _LOGGER.debug( "Updated SmartApp '%s' under parent app '%s'", req.installed_app_id, app.app_id ) diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index dc046f718a8..81dbab917a3 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -8,28 +8,30 @@ from pysmartthings.installedapp import format_install_url from homeassistant import data_entry_flow from homeassistant.components.smartthings import smartapp -from homeassistant.components.smartthings.config_flow import SmartThingsFlowHandler from homeassistant.components.smartthings.const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, CONF_OAUTH_CLIENT_ID, CONF_OAUTH_CLIENT_SECRET, - CONF_REFRESH_TOKEN, DOMAIN, ) -from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN, HTTP_NOT_FOUND +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + HTTP_FORBIDDEN, + HTTP_NOT_FOUND, + HTTP_UNAUTHORIZED, +) from tests.common import MockConfigEntry, mock_coro -async def test_step_import(hass): - """Test import returns user.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_import() - +async def test_import_shows_user_step(hass): + """Test import source shows the user form.""" + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "import"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["description_placeholders"][ @@ -37,237 +39,316 @@ async def test_step_import(hass): ] == smartapp.get_webhook_url(hass) -async def test_step_user(hass): - """Test the webhook confirmation is shown.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_user() - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - assert result["description_placeholders"][ - "webhook_url" - ] == smartapp.get_webhook_url(hass) - - -async def test_step_user_aborts_invalid_webhook(hass): - """Test flow aborts if webhook is invalid.""" - hass.config.api.base_url = "http://0.0.0.0" - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_user() - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "invalid_webhook_url" - assert result["description_placeholders"][ - "webhook_url" - ] == smartapp.get_webhook_url(hass) - assert "component_url" in result["description_placeholders"] - - -async def test_step_user_advances_to_pat(hass): - """Test user step advances to the pat step.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_user({}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - - -async def test_step_pat(hass): - """Test pat step shows the input form.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_pat() - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {} - assert result["data_schema"]({CONF_ACCESS_TOKEN: ""}) == {CONF_ACCESS_TOKEN: ""} - assert "token_url" in result["description_placeholders"] - assert "component_url" in result["description_placeholders"] - - -async def test_step_pat_defaults_token(hass): - """Test pat form defaults the token from another entry.""" +async def test_entry_created(hass, app, app_oauth_client, location, smartthings_mock): + """Test local webhook, new app, install event creates entry.""" token = str(uuid4()) - entry = MockConfigEntry(domain=DOMAIN, data={CONF_ACCESS_TOKEN: token}) - entry.add_to_hass(hass) - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_pat() - - assert flow.access_token == token - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - assert "token_url" in result["description_placeholders"] - assert "component_url" in result["description_placeholders"] - - -async def test_step_pat_invalid_token(hass): - """Test an error is shown for invalid token formats.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - token = "123456789" - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - assert result["errors"] == {"access_token": "token_invalid_format"} - assert "token_url" in result["description_placeholders"] - assert "component_url" in result["description_placeholders"] - - -async def test_step_pat_unauthorized(hass, smartthings_mock): - """Test an error is shown when the token is not authorized.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - request_info = Mock(real_url="http://example.com") - smartthings_mock.apps.side_effect = ClientResponseError( - request_info=request_info, history=None, status=401 - ) - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {CONF_ACCESS_TOKEN: "token_unauthorized"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_forbidden(hass, smartthings_mock): - """Test an error is shown when the token is forbidden.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - request_info = Mock(real_url="http://example.com") - smartthings_mock.apps.side_effect = ClientResponseError( - request_info=request_info, history=None, status=HTTP_FORBIDDEN - ) - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {CONF_ACCESS_TOKEN: "token_forbidden"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_webhook_error(hass, smartthings_mock): - """Test an error is shown when there's an problem with the webhook endpoint.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - data = {"error": {}} - request_info = Mock(real_url="http://example.com") - error = APIResponseError( - request_info=request_info, history=None, data=data, status=422 - ) - error.is_target_error = Mock(return_value=True) - smartthings_mock.apps.side_effect = error - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {"base": "webhook_error"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_api_error(hass, smartthings_mock): - """Test an error is shown when other API errors occur.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - data = {"error": {}} - request_info = Mock(real_url="http://example.com") - error = APIResponseError( - request_info=request_info, history=None, data=data, status=400 - ) - smartthings_mock.apps.side_effect = error - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {"base": "app_setup_error"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_unknown_api_error(hass, smartthings_mock): - """Test an error is shown when there is an unknown API error.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - request_info = Mock(real_url="http://example.com") - smartthings_mock.apps.side_effect = ClientResponseError( - request_info=request_info, history=None, status=HTTP_NOT_FOUND - ) - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {"base": "app_setup_error"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_unknown_error(hass, smartthings_mock): - """Test an error is shown when there is an unknown API error.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - smartthings_mock.apps.side_effect = Exception("Unknown error") - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "pat" - assert result["errors"] == {"base": "app_setup_error"} - assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} - - -async def test_step_pat_app_created_webhook( - hass, app, app_oauth_client, location, smartthings_mock -): - """Test SmartApp is created when one does not exist and shows location form.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - + installed_app_id = str(uuid4()) + refresh_token = str(uuid4()) smartthings_mock.apps.return_value = [] smartthings_mock.create_app.return_value = (app, app_oauth_client) smartthings_mock.locations.return_value = [location] - token = str(uuid4()) + request = Mock() + request.installed_app_id = installed_app_id + request.auth_token = token + request.location_id = location.location_id + request.refresh_token = refresh_token - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) - assert flow.access_token == token - assert flow.app_id == app.app_id - assert flow.oauth_client_secret == app_oauth_client.client_secret - assert flow.oauth_client_id == app_oauth_client.client_id + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "select_location" + # Select location and advance to external auth + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_LOCATION_ID: location.location_id} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "authorize" + assert result["url"] == format_install_url(app.app_id, location.location_id) -async def test_step_pat_app_created_cloudhook( + # Complete external auth and advance to install + await smartapp.smartapp_install(hass, request, None, app) + + # Finish + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["app_id"] == app.app_id + assert result["data"]["installed_app_id"] == installed_app_id + assert result["data"]["location_id"] == location.location_id + assert result["data"]["access_token"] == token + assert result["data"]["refresh_token"] == request.refresh_token + assert result["data"]["client_secret"] == app_oauth_client.client_secret + assert result["data"]["client_id"] == app_oauth_client.client_id + assert result["title"] == location.name + entry = next((entry for entry in hass.config_entries.async_entries(DOMAIN)), None,) + assert entry.unique_id == smartapp.format_unique_id( + app.app_id, location.location_id + ) + + +async def test_entry_created_from_update_event( hass, app, app_oauth_client, location, smartthings_mock ): - """Test SmartApp is created with a cloudhook and shows location form.""" - hass.config.components.add("cloud") + """Test local webhook, new app, update event creates entry.""" + token = str(uuid4()) + installed_app_id = str(uuid4()) + refresh_token = str(uuid4()) + smartthings_mock.apps.return_value = [] + smartthings_mock.create_app.return_value = (app, app_oauth_client) + smartthings_mock.locations.return_value = [location] + request = Mock() + request.installed_app_id = installed_app_id + request.auth_token = token + request.location_id = location.location_id + request.refresh_token = refresh_token + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "select_location" + + # Select location and advance to external auth + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_LOCATION_ID: location.location_id} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "authorize" + assert result["url"] == format_install_url(app.app_id, location.location_id) + + # Complete external auth and advance to install + await smartapp.smartapp_update(hass, request, None, app) + + # Finish + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["app_id"] == app.app_id + assert result["data"]["installed_app_id"] == installed_app_id + assert result["data"]["location_id"] == location.location_id + assert result["data"]["access_token"] == token + assert result["data"]["refresh_token"] == request.refresh_token + assert result["data"]["client_secret"] == app_oauth_client.client_secret + assert result["data"]["client_id"] == app_oauth_client.client_id + assert result["title"] == location.name + entry = next((entry for entry in hass.config_entries.async_entries(DOMAIN)), None,) + assert entry.unique_id == smartapp.format_unique_id( + app.app_id, location.location_id + ) + + +async def test_entry_created_existing_app_new_oauth_client( + hass, app, app_oauth_client, location, smartthings_mock +): + """Test entry is created with an existing app and generation of a new oauth client.""" + token = str(uuid4()) + installed_app_id = str(uuid4()) + refresh_token = str(uuid4()) + smartthings_mock.apps.return_value = [app] + smartthings_mock.generate_app_oauth.return_value = app_oauth_client + smartthings_mock.locations.return_value = [location] + request = Mock() + request.installed_app_id = installed_app_id + request.auth_token = token + request.location_id = location.location_id + request.refresh_token = refresh_token + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "select_location" + + # Select location and advance to external auth + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_LOCATION_ID: location.location_id} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "authorize" + assert result["url"] == format_install_url(app.app_id, location.location_id) + + # Complete external auth and advance to install + await smartapp.smartapp_install(hass, request, None, app) + + # Finish + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["app_id"] == app.app_id + assert result["data"]["installed_app_id"] == installed_app_id + assert result["data"]["location_id"] == location.location_id + assert result["data"]["access_token"] == token + assert result["data"]["refresh_token"] == request.refresh_token + assert result["data"]["client_secret"] == app_oauth_client.client_secret + assert result["data"]["client_id"] == app_oauth_client.client_id + assert result["title"] == location.name + entry = next((entry for entry in hass.config_entries.async_entries(DOMAIN)), None,) + assert entry.unique_id == smartapp.format_unique_id( + app.app_id, location.location_id + ) + + +async def test_entry_created_existing_app_copies_oauth_client( + hass, app, location, smartthings_mock +): + """Test entry is created with an existing app and copies the oauth client from another entry.""" + token = str(uuid4()) + installed_app_id = str(uuid4()) + refresh_token = str(uuid4()) + oauth_client_id = str(uuid4()) + oauth_client_secret = str(uuid4()) + smartthings_mock.apps.return_value = [app] + smartthings_mock.locations.return_value = [location] + request = Mock() + request.installed_app_id = installed_app_id + request.auth_token = token + request.location_id = location.location_id + request.refresh_token = refresh_token + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_APP_ID: app.app_id, + CONF_OAUTH_CLIENT_ID: oauth_client_id, + CONF_OAUTH_CLIENT_SECRET: oauth_client_secret, + CONF_LOCATION_ID: str(uuid4()), + CONF_INSTALLED_APP_ID: str(uuid4()), + CONF_ACCESS_TOKEN: token, + }, + ) + entry.add_to_hass(hass) + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + # Assert access token is defaulted to an existing entry for convenience. + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "select_location" + + # Select location and advance to external auth + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_LOCATION_ID: location.location_id} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "authorize" + assert result["url"] == format_install_url(app.app_id, location.location_id) + + # Complete external auth and advance to install + await smartapp.smartapp_install(hass, request, None, app) + + # Finish + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["app_id"] == app.app_id + assert result["data"]["installed_app_id"] == installed_app_id + assert result["data"]["location_id"] == location.location_id + assert result["data"]["access_token"] == token + assert result["data"]["refresh_token"] == request.refresh_token + assert result["data"]["client_secret"] == oauth_client_secret + assert result["data"]["client_id"] == oauth_client_id + assert result["title"] == location.name + entry = next( + ( + entry + for entry in hass.config_entries.async_entries(DOMAIN) + if entry.data[CONF_INSTALLED_APP_ID] == installed_app_id + ), + None, + ) + assert entry.unique_id == smartapp.format_unique_id( + app.app_id, location.location_id + ) + + +async def test_entry_created_with_cloudhook( + hass, app, app_oauth_client, location, smartthings_mock +): + """Test cloud, new app, install event creates entry.""" + hass.config.components.add("cloud") # Unload the endpoint so we can reload it under the cloud. await smartapp.unload_smartapp_endpoint(hass) + token = str(uuid4()) + installed_app_id = str(uuid4()) + refresh_token = str(uuid4()) + smartthings_mock.apps.return_value = [] + smartthings_mock.create_app.return_value = (app, app_oauth_client) + smartthings_mock.locations.return_value = [location] + request = Mock() + request.installed_app_id = installed_app_id + request.auth_token = token + request.location_id = location.location_id + request.refresh_token = refresh_token with patch.object( hass.components.cloud, "async_active_subscription", return_value=True @@ -279,163 +360,368 @@ async def test_step_pat_app_created_cloudhook( await smartapp.setup_smartapp_endpoint(hass) - flow = SmartThingsFlowHandler() - flow.hass = hass - smartthings_mock.apps.return_value = [] - smartthings_mock.create_app.return_value = (app, app_oauth_client) - smartthings_mock.locations.return_value = [location] - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert flow.access_token == token - assert flow.app_id == app.app_id - assert flow.oauth_client_secret == app_oauth_client.client_secret - assert flow.oauth_client_id == app_oauth_client.client_id + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "select_location" + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) assert mock_create_cloudhook.call_count == 1 + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] -async def test_step_pat_app_updated_webhook( + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "select_location" + + # Select location and advance to external auth + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_LOCATION_ID: location.location_id} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP + assert result["step_id"] == "authorize" + assert result["url"] == format_install_url(app.app_id, location.location_id) + + # Complete external auth and advance to install + await smartapp.smartapp_install(hass, request, None, app) + + # Finish + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"]["app_id"] == app.app_id + assert result["data"]["installed_app_id"] == installed_app_id + assert result["data"]["location_id"] == location.location_id + assert result["data"]["access_token"] == token + assert result["data"]["refresh_token"] == request.refresh_token + assert result["data"]["client_secret"] == app_oauth_client.client_secret + assert result["data"]["client_id"] == app_oauth_client.client_id + assert result["title"] == location.name + entry = next( + (entry for entry in hass.config_entries.async_entries(DOMAIN)), None, + ) + assert entry.unique_id == smartapp.format_unique_id( + app.app_id, location.location_id + ) + + +async def test_invalid_webhook_aborts(hass): + """Test flow aborts if webhook is invalid.""" + hass.config.api.base_url = "http://0.0.0.0" + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "invalid_webhook_url" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + assert "component_url" in result["description_placeholders"] + + +async def test_invalid_token_shows_error(hass): + """Test an error is shown for invalid token formats.""" + token = "123456789" + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {CONF_ACCESS_TOKEN: "token_invalid_format"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_unauthorized_token_shows_error(hass, smartthings_mock): + """Test an error is shown for unauthorized token formats.""" + token = str(uuid4()) + request_info = Mock(real_url="http://example.com") + smartthings_mock.apps.side_effect = ClientResponseError( + request_info=request_info, history=None, status=HTTP_UNAUTHORIZED + ) + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {CONF_ACCESS_TOKEN: "token_unauthorized"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_forbidden_token_shows_error(hass, smartthings_mock): + """Test an error is shown for forbidden token formats.""" + token = str(uuid4()) + request_info = Mock(real_url="http://example.com") + smartthings_mock.apps.side_effect = ClientResponseError( + request_info=request_info, history=None, status=HTTP_FORBIDDEN + ) + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {CONF_ACCESS_TOKEN: "token_forbidden"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_webhook_problem_shows_error(hass, smartthings_mock): + """Test an error is shown when there's an problem with the webhook endpoint.""" + token = str(uuid4()) + data = {"error": {}} + request_info = Mock(real_url="http://example.com") + error = APIResponseError( + request_info=request_info, history=None, data=data, status=422 + ) + error.is_target_error = Mock(return_value=True) + smartthings_mock.apps.side_effect = error + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {"base": "webhook_error"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_api_error_shows_error(hass, smartthings_mock): + """Test an error is shown when other API errors occur.""" + token = str(uuid4()) + data = {"error": {}} + request_info = Mock(real_url="http://example.com") + error = APIResponseError( + request_info=request_info, history=None, data=data, status=400 + ) + smartthings_mock.apps.side_effect = error + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {"base": "app_setup_error"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_unknown_response_error_shows_error(hass, smartthings_mock): + """Test an error is shown when there is an unknown API error.""" + token = str(uuid4()) + request_info = Mock(real_url="http://example.com") + error = ClientResponseError( + request_info=request_info, history=None, status=HTTP_NOT_FOUND + ) + smartthings_mock.apps.side_effect = error + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {"base": "app_setup_error"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_unknown_error_shows_error(hass, smartthings_mock): + """Test an error is shown when there is an unknown API error.""" + token = str(uuid4()) + smartthings_mock.apps.side_effect = Exception("Unknown error") + + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert result["data_schema"]({}) == {CONF_ACCESS_TOKEN: token} + assert result["errors"] == {"base": "app_setup_error"} + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + +async def test_no_available_locations_aborts( hass, app, app_oauth_client, location, smartthings_mock ): - """Test SmartApp is updated then show location form.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - - smartthings_mock.apps.return_value = [app] - smartthings_mock.generate_app_oauth.return_value = app_oauth_client - smartthings_mock.locations.return_value = [location] - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert flow.access_token == token - assert flow.app_id == app.app_id - assert flow.oauth_client_secret == app_oauth_client.client_secret - assert flow.oauth_client_id == app_oauth_client.client_id - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "select_location" - - -async def test_step_pat_app_updated_webhook_from_existing_oauth_client( - hass, app, location, smartthings_mock -): - """Test SmartApp is updated from existing then show location form.""" - oauth_client_id = str(uuid4()) - oauth_client_secret = str(uuid4()) - entry = MockConfigEntry( - domain=DOMAIN, - data={ - CONF_APP_ID: app.app_id, - CONF_OAUTH_CLIENT_ID: oauth_client_id, - CONF_OAUTH_CLIENT_SECRET: oauth_client_secret, - CONF_LOCATION_ID: str(uuid4()), - }, - ) - entry.add_to_hass(hass) - flow = SmartThingsFlowHandler() - flow.hass = hass - smartthings_mock.apps.return_value = [app] - smartthings_mock.locations.return_value = [location] - token = str(uuid4()) - - result = await flow.async_step_pat({CONF_ACCESS_TOKEN: token}) - - assert flow.access_token == token - assert flow.app_id == app.app_id - assert flow.oauth_client_secret == oauth_client_secret - assert flow.oauth_client_id == oauth_client_id - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "select_location" - - -async def test_step_select_location(hass, location, smartthings_mock): - """Test select location shows form with available locations.""" - smartthings_mock.locations.return_value = [location] - flow = SmartThingsFlowHandler() - flow.hass = hass - flow.api = smartthings_mock - - result = await flow.async_step_select_location() - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "select_location" - assert result["data_schema"]({CONF_LOCATION_ID: location.location_id}) == { - CONF_LOCATION_ID: location.location_id - } - - -async def test_step_select_location_aborts(hass, location, smartthings_mock): """Test select location aborts if no available locations.""" + token = str(uuid4()) + smartthings_mock.apps.return_value = [] + smartthings_mock.create_app.return_value = (app, app_oauth_client) smartthings_mock.locations.return_value = [location] entry = MockConfigEntry( domain=DOMAIN, data={CONF_LOCATION_ID: location.location_id} ) entry.add_to_hass(hass) - flow = SmartThingsFlowHandler() - flow.hass = hass - flow.api = smartthings_mock - result = await flow.async_step_select_location() + # Webhook confirmation shown + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["description_placeholders"][ + "webhook_url" + ] == smartapp.get_webhook_url(hass) + + # Advance to PAT screen + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "pat" + assert "token_url" in result["description_placeholders"] + assert "component_url" in result["description_placeholders"] + + # Enter token and advance to location screen + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_ACCESS_TOKEN: token} + ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "no_available_locations" - - -async def test_step_select_location_advances(hass): - """Test select location aborts if no available locations.""" - location_id = str(uuid4()) - app_id = str(uuid4()) - flow = SmartThingsFlowHandler() - flow.hass = hass - flow.app_id = app_id - - result = await flow.async_step_select_location({CONF_LOCATION_ID: location_id}) - - assert flow.location_id == location_id - assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP - assert result["step_id"] == "authorize" - assert result["url"] == format_install_url(app_id, location_id) - - -async def test_step_authorize_advances(hass): - """Test authorize step advances when completed.""" - installed_app_id = str(uuid4()) - refresh_token = str(uuid4()) - flow = SmartThingsFlowHandler() - flow.hass = hass - - result = await flow.async_step_authorize( - {CONF_INSTALLED_APP_ID: installed_app_id, CONF_REFRESH_TOKEN: refresh_token} - ) - - assert flow.installed_app_id == installed_app_id - assert flow.refresh_token == refresh_token - assert result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP_DONE - assert result["step_id"] == "install" - - -async def test_step_install_creates_entry(hass, location, smartthings_mock): - """Test a config entry is created once the app is installed.""" - flow = SmartThingsFlowHandler() - flow.hass = hass - flow.api = smartthings_mock - flow.access_token = str(uuid4()) - flow.app_id = str(uuid4()) - flow.installed_app_id = str(uuid4()) - flow.location_id = location.location_id - flow.oauth_client_id = str(uuid4()) - flow.oauth_client_secret = str(uuid4()) - flow.refresh_token = str(uuid4()) - - result = await flow.async_step_install() - - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["data"]["app_id"] == flow.app_id - assert result["data"]["installed_app_id"] == flow.installed_app_id - assert result["data"]["location_id"] == flow.location_id - assert result["data"]["access_token"] == flow.access_token - assert result["data"]["refresh_token"] == flow.refresh_token - assert result["data"]["client_secret"] == flow.oauth_client_secret - assert result["data"]["client_id"] == flow.oauth_client_id - assert result["title"] == location.name diff --git a/tests/components/smartthings/test_smartapp.py b/tests/components/smartthings/test_smartapp.py index 4d7280a6a9e..efc4844cef2 100644 --- a/tests/components/smartthings/test_smartapp.py +++ b/tests/components/smartthings/test_smartapp.py @@ -6,8 +6,6 @@ from pysmartthings import AppEntity, Capability from homeassistant.components.smartthings import smartapp from homeassistant.components.smartthings.const import ( - CONF_INSTALLED_APP_ID, - CONF_LOCATION_ID, CONF_REFRESH_TOKEN, DATA_MANAGER, DOMAIN, @@ -39,36 +37,6 @@ async def test_update_app_updated_needed(hass, app): assert mock_app.classifications == app.classifications -async def test_smartapp_install_configures_flow(hass): - """Test install event continues an existing flow.""" - # Arrange - flow_id = str(uuid4()) - flows = [{"flow_id": flow_id, "handler": DOMAIN}] - app = Mock() - app.app_id = uuid4() - request = Mock() - request.installed_app_id = str(uuid4()) - request.auth_token = str(uuid4()) - request.location_id = str(uuid4()) - request.refresh_token = str(uuid4()) - - # Act - with patch.object( - hass.config_entries.flow, "async_progress", return_value=flows - ), patch.object(hass.config_entries.flow, "async_configure") as configure_mock: - - await smartapp.smartapp_install(hass, request, None, app) - - configure_mock.assert_called_once_with( - flow_id, - { - CONF_INSTALLED_APP_ID: request.installed_app_id, - CONF_LOCATION_ID: request.location_id, - CONF_REFRESH_TOKEN: request.refresh_token, - }, - ) - - async def test_smartapp_update_saves_token( hass, smartthings_mock, location, device_factory ): @@ -92,36 +60,6 @@ async def test_smartapp_update_saves_token( assert entry.data[CONF_REFRESH_TOKEN] == request.refresh_token -async def test_smartapp_update_configures_flow(hass): - """Test update event continues an existing flow.""" - # Arrange - flow_id = str(uuid4()) - flows = [{"flow_id": flow_id, "handler": DOMAIN}] - app = Mock() - app.app_id = uuid4() - request = Mock() - request.installed_app_id = str(uuid4()) - request.auth_token = str(uuid4()) - request.location_id = str(uuid4()) - request.refresh_token = str(uuid4()) - - # Act - with patch.object( - hass.config_entries.flow, "async_progress", return_value=flows - ), patch.object(hass.config_entries.flow, "async_configure") as configure_mock: - - await smartapp.smartapp_update(hass, request, None, app) - - configure_mock.assert_called_once_with( - flow_id, - { - CONF_INSTALLED_APP_ID: request.installed_app_id, - CONF_LOCATION_ID: request.location_id, - CONF_REFRESH_TOKEN: request.refresh_token, - }, - ) - - async def test_smartapp_uninstall(hass, config_entry): """Test the config entry is unloaded when the app is uninstalled.""" config_entry.add_to_hass(hass) From 6c18a2cae272ec6aad9f226c8b55aaca73a38966 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 16:24:57 -0500 Subject: [PATCH 142/511] Config flow for hunterdouglas_powerview (#34795) Co-Authored-By: Paulus Schoutsen --- .coveragerc | 3 + CODEOWNERS | 1 + .../hunterdouglas_powerview/__init__.py | 195 ++++++++++- .../hunterdouglas_powerview/config_flow.py | 135 ++++++++ .../hunterdouglas_powerview/const.py | 65 ++++ .../hunterdouglas_powerview/cover.py | 306 ++++++++++++++++++ .../hunterdouglas_powerview/entity.py | 59 ++++ .../hunterdouglas_powerview/manifest.json | 12 +- .../hunterdouglas_powerview/scene.py | 99 +++--- .../hunterdouglas_powerview/strings.json | 24 ++ .../translations/en.json | 25 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/zeroconf.py | 1 + requirements_test_all.txt | 3 + .../hunterdouglas_powerview/__init__.py | 1 + .../test_config_flow.py | 220 +++++++++++++ .../hunterdouglas_powerview/userdata.json | 50 +++ 17 files changed, 1140 insertions(+), 60 deletions(-) create mode 100644 homeassistant/components/hunterdouglas_powerview/config_flow.py create mode 100644 homeassistant/components/hunterdouglas_powerview/const.py create mode 100644 homeassistant/components/hunterdouglas_powerview/cover.py create mode 100644 homeassistant/components/hunterdouglas_powerview/entity.py create mode 100644 homeassistant/components/hunterdouglas_powerview/strings.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/en.json create mode 100644 tests/components/hunterdouglas_powerview/__init__.py create mode 100644 tests/components/hunterdouglas_powerview/test_config_flow.py create mode 100644 tests/fixtures/hunterdouglas_powerview/userdata.json diff --git a/.coveragerc b/.coveragerc index 2aabc0d8028..cf9fa59397d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -311,7 +311,10 @@ omit = homeassistant/components/huawei_lte/* homeassistant/components/huawei_router/device_tracker.py homeassistant/components/hue/light.py + homeassistant/components/hunterdouglas_powerview/__init__.py homeassistant/components/hunterdouglas_powerview/scene.py + homeassistant/components/hunterdouglas_powerview/cover.py + homeassistant/components/hunterdouglas_powerview/entity.py homeassistant/components/hydrawise/* homeassistant/components/hyperion/light.py homeassistant/components/ialarm/alarm_control_panel.py diff --git a/CODEOWNERS b/CODEOWNERS index f60da8494d7..51ce87e702f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -174,6 +174,7 @@ homeassistant/components/http/* @home-assistant/core homeassistant/components/huawei_lte/* @scop homeassistant/components/huawei_router/* @abmantis homeassistant/components/hue/* @balloob +homeassistant/components/hunterdouglas_powerview/* @bdraco homeassistant/components/iammeter/* @lewei50 homeassistant/components/iaqualink/* @flz homeassistant/components/icloud/* @Quentame diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 14ede545576..44ebf25a4f4 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -1 +1,194 @@ -"""The hunterdouglas_powerview component.""" +"""The Hunter Douglas PowerView integration.""" +import asyncio +from datetime import timedelta +import logging + +from aiopvapi.helpers.aiorequest import AioRequest +from aiopvapi.helpers.constants import ATTR_ID +from aiopvapi.helpers.tools import base64_to_unicode +from aiopvapi.rooms import Rooms +from aiopvapi.scenes import Scenes +from aiopvapi.shades import Shades +from aiopvapi.userdata import UserData +import async_timeout +import voluptuous as vol + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import ( + COORDINATOR, + DEVICE_FIRMWARE, + DEVICE_INFO, + DEVICE_MAC_ADDRESS, + DEVICE_MODEL, + DEVICE_NAME, + DEVICE_REVISION, + DEVICE_SERIAL_NUMBER, + DOMAIN, + FIRMWARE_IN_USERDATA, + HUB_EXCEPTIONS, + HUB_NAME, + MAC_ADDRESS_IN_USERDATA, + MAINPROCESSOR_IN_USERDATA_FIRMWARE, + MODEL_IN_MAINPROCESSOR, + PV_API, + PV_ROOM_DATA, + PV_SCENE_DATA, + PV_SHADE_DATA, + PV_SHADES, + REVISION_IN_MAINPROCESSOR, + ROOM_DATA, + SCENE_DATA, + SERIAL_NUMBER_IN_USERDATA, + SHADE_DATA, + USER_DATA, +) + +DEVICE_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA +) + + +def _has_all_unique_hosts(value): + """Validate that each hub configured has a unique host.""" + hosts = [device[CONF_HOST] for device in value] + schema = vol.Schema(vol.Unique()) + schema(hosts) + return value + + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA], _has_all_unique_hosts)}, + extra=vol.ALLOW_EXTRA, +) + + +PLATFORMS = ["cover", "scene"] +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, hass_config: dict): + """Set up the Hunter Douglas PowerView component.""" + hass.data.setdefault(DOMAIN, {}) + + if DOMAIN not in hass_config: + return True + + for conf in hass_config[DOMAIN]: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Hunter Douglas PowerView from a config entry.""" + + config = entry.data + + hub_address = config.get(CONF_HOST) + websession = async_get_clientsession(hass) + + pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession) + + try: + async with async_timeout.timeout(10): + device_info = await async_get_device_info(pv_request) + except HUB_EXCEPTIONS: + _LOGGER.error("Connection error to PowerView hub: %s", hub_address) + raise ConfigEntryNotReady + if not device_info: + _LOGGER.error("Unable to initialize PowerView hub: %s", hub_address) + raise ConfigEntryNotReady + + rooms = Rooms(pv_request) + room_data = _async_map_data_by_id((await rooms.get_resources())[ROOM_DATA]) + + scenes = Scenes(pv_request) + scene_data = _async_map_data_by_id((await scenes.get_resources())[SCENE_DATA]) + + shades = Shades(pv_request) + shade_data = _async_map_data_by_id((await shades.get_resources())[SHADE_DATA]) + + async def async_update_data(): + """Fetch data from shade endpoint.""" + async with async_timeout.timeout(10): + shade_entries = await shades.get_resources() + if not shade_entries: + raise UpdateFailed(f"Failed to fetch new shade data.") + return _async_map_data_by_id(shade_entries[SHADE_DATA]) + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name="powerview hub", + update_method=async_update_data, + update_interval=timedelta(seconds=60), + ) + + hass.data[DOMAIN][entry.entry_id] = { + PV_API: pv_request, + PV_ROOM_DATA: room_data, + PV_SCENE_DATA: scene_data, + PV_SHADES: shades, + PV_SHADE_DATA: shade_data, + COORDINATOR: coordinator, + DEVICE_INFO: device_info, + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_get_device_info(pv_request): + """Determine device info.""" + userdata = UserData(pv_request) + resources = await userdata.get_resources() + userdata_data = resources[USER_DATA] + + main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][ + MAINPROCESSOR_IN_USERDATA_FIRMWARE + ] + return { + DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]), + DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA], + DEVICE_SERIAL_NUMBER: userdata_data[SERIAL_NUMBER_IN_USERDATA], + DEVICE_REVISION: main_processor_info[REVISION_IN_MAINPROCESSOR], + DEVICE_FIRMWARE: main_processor_info, + DEVICE_MODEL: main_processor_info[MODEL_IN_MAINPROCESSOR], + } + + +@callback +def _async_map_data_by_id(data): + """Return a dict with the key being the id for a list of entries.""" + return {entry[ATTR_ID]: entry for entry in data} + + +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/hunterdouglas_powerview/config_flow.py b/homeassistant/components/hunterdouglas_powerview/config_flow.py new file mode 100644 index 00000000000..52a70b85d2e --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/config_flow.py @@ -0,0 +1,135 @@ +"""Config flow for Hunter Douglas PowerView integration.""" +import logging + +from aiopvapi.helpers.aiorequest import AioRequest +import async_timeout +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from . import async_get_device_info +from .const import DEVICE_NAME, DEVICE_SERIAL_NUMBER, HUB_EXCEPTIONS +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str}) +HAP_SUFFIX = "._hap._tcp.local." + + +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. + """ + + hub_address = data[CONF_HOST] + websession = async_get_clientsession(hass) + + pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession) + + try: + async with async_timeout.timeout(10): + device_info = await async_get_device_info(pv_request) + except HUB_EXCEPTIONS: + raise CannotConnect + if not device_info: + raise CannotConnect + + # Return info that you want to store in the config entry. + return { + "title": device_info[DEVICE_NAME], + "unique_id": device_info[DEVICE_SERIAL_NUMBER], + } + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Hunter Douglas PowerView.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize the powerview config flow.""" + self.powerview_config = {} + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + if self._host_already_configured(user_input[CONF_HOST]): + return self.async_abort(reason="already_configured") + 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 not errors: + await self.async_set_unique_id(info["unique_id"]) + return self.async_create_entry( + title=info["title"], data={CONF_HOST: user_input[CONF_HOST]} + ) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + async def async_step_import(self, user_input=None): + """Handle the initial step.""" + return await self.async_step_user(user_input) + + async def async_step_homekit(self, homekit_info): + """Handle HomeKit discovery.""" + + # If we already have the host configured do + # not open connections to it if we can avoid it. + if self._host_already_configured(homekit_info[CONF_HOST]): + return self.async_abort(reason="already_configured") + + try: + info = await validate_input(self.hass, homekit_info) + except CannotConnect: + return self.async_abort(reason="cannot_connect") + except Exception: # pylint: disable=broad-except + return self.async_abort(reason="unknown") + + await self.async_set_unique_id(info["unique_id"], raise_on_progress=False) + self._abort_if_unique_id_configured({CONF_HOST: homekit_info["host"]}) + + name = homekit_info["name"] + if name.endswith(HAP_SUFFIX): + name = name[: -len(HAP_SUFFIX)] + + self.powerview_config = { + CONF_HOST: homekit_info["host"], + CONF_NAME: name, + } + return await self.async_step_link() + + async def async_step_link(self, user_input=None): + """Attempt to link with Powerview.""" + if user_input is not None: + return self.async_create_entry( + title=self.powerview_config[CONF_NAME], + data={CONF_HOST: self.powerview_config[CONF_HOST]}, + ) + + return self.async_show_form( + step_id="link", description_placeholders=self.powerview_config + ) + + def _host_already_configured(self, host): + """See if we already have a hub with the host address configured.""" + existing_hosts = { + entry.data[CONF_HOST] for entry in self._async_current_entries() + } + return host in existing_hosts + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py new file mode 100644 index 00000000000..9979cfb186c --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -0,0 +1,65 @@ +"""Support for Powerview scenes from a Powerview hub.""" + +import asyncio + +from aiopvapi.helpers.aiorequest import PvApiConnectionError + +DOMAIN = "hunterdouglas_powerview" + + +MANUFACTURER = "Hunter Douglas" + +HUB_ADDRESS = "address" + +SCENE_DATA = "sceneData" +SHADE_DATA = "shadeData" +ROOM_DATA = "roomData" +USER_DATA = "userData" + +MAC_ADDRESS_IN_USERDATA = "macAddress" +SERIAL_NUMBER_IN_USERDATA = "serialNumber" +FIRMWARE_IN_USERDATA = "firmware" +MAINPROCESSOR_IN_USERDATA_FIRMWARE = "mainProcessor" +REVISION_IN_MAINPROCESSOR = "revision" +MODEL_IN_MAINPROCESSOR = "name" +HUB_NAME = "hubName" + +FIRMWARE_IN_SHADE = "firmware" + +FIRMWARE_REVISION = "revision" +FIRMWARE_SUB_REVISION = "subRevision" +FIRMWARE_BUILD = "build" + +DEVICE_NAME = "device_name" +DEVICE_MAC_ADDRESS = "device_mac_address" +DEVICE_SERIAL_NUMBER = "device_serial_number" +DEVICE_REVISION = "device_revision" +DEVICE_INFO = "device_info" +DEVICE_MODEL = "device_model" +DEVICE_FIRMWARE = "device_firmware" + +SCENE_NAME = "name" +SCENE_ID = "id" +ROOM_ID_IN_SCENE = "roomId" + +SHADE_NAME = "name" +SHADE_ID = "id" +ROOM_ID_IN_SHADE = "roomId" + +ROOM_NAME = "name" +ROOM_NAME_UNICODE = "name_unicode" +ROOM_ID = "id" + +SHADE_RESPONSE = "shade" + +STATE_ATTRIBUTE_ROOM_NAME = "roomName" + +PV_API = "pv_api" +PV_HUB = "pv_hub" +PV_SHADES = "pv_shades" +PV_SCENE_DATA = "pv_scene_data" +PV_SHADE_DATA = "pv_shade_data" +PV_ROOM_DATA = "pv_room_data" +COORDINATOR = "coordinator" + +HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py new file mode 100644 index 00000000000..45fd798238f --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -0,0 +1,306 @@ +"""Support for hunter douglas shades.""" +import asyncio +import logging + +from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA +from aiopvapi.resources.shade import ( + ATTR_POSKIND1, + ATTR_TYPE, + MAX_POSITION, + MIN_POSITION, + factory as PvShade, +) +import async_timeout + +from homeassistant.components.cover import ( + ATTR_POSITION, + DEVICE_CLASS_SHADE, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, + SUPPORT_STOP, + CoverEntity, +) +from homeassistant.core import callback +from homeassistant.helpers.event import async_call_later + +from .const import ( + COORDINATOR, + DEVICE_INFO, + DEVICE_MODEL, + DEVICE_SERIAL_NUMBER, + DOMAIN, + FIRMWARE_BUILD, + FIRMWARE_IN_SHADE, + FIRMWARE_REVISION, + FIRMWARE_SUB_REVISION, + MANUFACTURER, + PV_API, + PV_ROOM_DATA, + PV_SHADE_DATA, + ROOM_ID_IN_SHADE, + ROOM_NAME_UNICODE, + SHADE_RESPONSE, + STATE_ATTRIBUTE_ROOM_NAME, +) +from .entity import HDEntity + +_LOGGER = logging.getLogger(__name__) + +# Estimated time it takes to complete a transition +# from one state to another +TRANSITION_COMPLETE_DURATION = 30 + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the hunter douglas shades.""" + + pv_data = hass.data[DOMAIN][entry.entry_id] + room_data = pv_data[PV_ROOM_DATA] + shade_data = pv_data[PV_SHADE_DATA] + pv_request = pv_data[PV_API] + coordinator = pv_data[COORDINATOR] + device_info = pv_data[DEVICE_INFO] + + entities = [] + for raw_shade in shade_data.values(): + # The shade may be out of sync with the hub + # so we force a refresh when we add it if + # possible + shade = PvShade(raw_shade, pv_request) + name_before_refresh = shade.name + try: + async with async_timeout.timeout(1): + await shade.refresh() + except asyncio.TimeoutError: + # Forced refresh is not required for setup + pass + entities.append( + PowerViewShade( + shade, name_before_refresh, room_data, coordinator, device_info + ) + ) + async_add_entities(entities) + + +def hd_position_to_hass(hd_position): + """Convert hunter douglas position to hass position.""" + return round((hd_position / MAX_POSITION) * 100) + + +def hass_position_to_hd(hass_positon): + """Convert hass position to hunter douglas position.""" + return int(hass_positon / 100 * MAX_POSITION) + + +class PowerViewShade(HDEntity, CoverEntity): + """Representation of a powerview shade.""" + + def __init__(self, shade, name, room_data, coordinator, device_info): + """Initialize the shade.""" + room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) + super().__init__(coordinator, device_info, shade.id) + self._shade = shade + self._device_info = device_info + self._is_opening = False + self._is_closing = False + self._room_name = None + self._last_action_timestamp = 0 + self._scheduled_transition_update = None + self._name = name + self._room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") + self._current_cover_position = MIN_POSITION + self._coordinator = coordinator + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return {STATE_ATTRIBUTE_ROOM_NAME: self._room_name} + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + if self._device_info[DEVICE_MODEL] != "1": + supported_features |= SUPPORT_STOP + return supported_features + + @property + def is_closed(self): + """Return if the cover is closed.""" + return self._current_cover_position == MIN_POSITION + + @property + def is_opening(self): + """Return if the cover is opening.""" + return self._is_opening + + @property + def is_closing(self): + """Return if the cover is closing.""" + return self._is_closing + + @property + def current_cover_position(self): + """Return the current position of cover.""" + return hd_position_to_hass(self._current_cover_position) + + @property + def device_class(self): + """Return device class.""" + return DEVICE_CLASS_SHADE + + @property + def name(self): + """Return the name of the shade.""" + return self._name + + async def async_close_cover(self, **kwargs): + """Close the cover.""" + await self._async_move(0) + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + await self._async_move(100) + + async def async_stop_cover(self, **kwargs): + """Stop the cover.""" + # Cancel any previous updates + self._async_cancel_scheduled_transition_update() + self._async_update_from_command(await self._shade.stop()) + await self._async_force_refresh_state() + + async def set_cover_position(self, **kwargs): + """Move the shade to a specific position.""" + if ATTR_POSITION not in kwargs: + return + await self._async_move(kwargs[ATTR_POSITION]) + + async def _async_move(self, target_hass_position): + """Move the shade to a position.""" + current_hass_position = hd_position_to_hass(self._current_cover_position) + steps_to_move = abs(current_hass_position - target_hass_position) + if not steps_to_move: + return + self._async_schedule_update_for_transition(steps_to_move) + self._async_update_from_command( + await self._shade.move( + { + ATTR_POSITION1: hass_position_to_hd(target_hass_position), + ATTR_POSKIND1: 1, + } + ) + ) + self._is_opening = False + self._is_closing = False + if target_hass_position > current_hass_position: + self._is_opening = True + elif target_hass_position < current_hass_position: + self._is_closing = True + self.async_write_ha_state() + + @callback + def _async_update_from_command(self, raw_data): + """Update the shade state after a command.""" + if not raw_data or SHADE_RESPONSE not in raw_data: + return + self._async_process_new_shade_data(raw_data[SHADE_RESPONSE]) + + @callback + def _async_process_new_shade_data(self, data): + """Process new data from an update.""" + self._shade.raw_data = data + self._async_update_current_cover_position() + + @callback + def _async_update_current_cover_position(self): + """Update the current cover position from the data.""" + _LOGGER.debug("Raw data update: %s", self._shade.raw_data) + position_data = self._shade.raw_data[ATTR_POSITION_DATA] + if ATTR_POSITION1 in position_data: + self._current_cover_position = position_data[ATTR_POSITION1] + self._is_opening = False + self._is_closing = False + + @callback + def _async_cancel_scheduled_transition_update(self): + """Cancel any previous updates.""" + if not self._scheduled_transition_update: + return + self._scheduled_transition_update() + self._scheduled_transition_update = None + + @callback + def _async_schedule_update_for_transition(self, steps): + self.async_write_ha_state() + + # Cancel any previous updates + self._async_cancel_scheduled_transition_update() + + est_time_to_complete_transition = 1 + int( + TRANSITION_COMPLETE_DURATION * (steps / 100) + ) + + _LOGGER.debug( + "Estimated time to complete transition of %s steps for %s: %s", + steps, + self.name, + est_time_to_complete_transition, + ) + + # Schedule an update for when we expect the transition + # to be completed. + self._scheduled_transition_update = async_call_later( + self.hass, + est_time_to_complete_transition, + self._async_complete_schedule_update, + ) + + async def _async_complete_schedule_update(self, _): + """Update status of the cover.""" + _LOGGER.debug("Processing scheduled update for %s", self.name) + self._scheduled_transition_update = None + await self._async_force_refresh_state() + + async def _async_force_refresh_state(self): + """Refresh the cover state and force the device cache to be bypassed.""" + await self._shade.refresh() + self._async_update_current_cover_position() + self.async_write_ha_state() + + @property + def device_info(self): + """Return the device_info of the device.""" + firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] + sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" + model = self._shade.raw_data[ATTR_TYPE] + for shade in self._shade.shade_types: + if shade.shade_type == model: + model = shade.description + break + + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.name, + "model": str(model), + "sw_version": sw_version, + "manufacturer": MANUFACTURER, + "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), + } + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self._async_update_current_cover_position() + self.async_on_remove( + self._coordinator.async_add_listener(self._async_update_shade_from_group) + ) + + @callback + def _async_update_shade_from_group(self): + """Update with new data from the coordinator.""" + if self._scheduled_transition_update: + # If a transition in in progress + # the data will be wrong + return + self._async_process_new_shade_data(self._coordinator.data[self._shade.id]) + self.async_write_ha_state() diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py new file mode 100644 index 00000000000..03d20e027b8 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -0,0 +1,59 @@ +"""The nexia integration base entity.""" + +import homeassistant.helpers.device_registry as dr +from homeassistant.helpers.entity import Entity + +from .const import ( + DEVICE_FIRMWARE, + DEVICE_MAC_ADDRESS, + DEVICE_MODEL, + DEVICE_NAME, + DEVICE_SERIAL_NUMBER, + DOMAIN, + FIRMWARE_BUILD, + FIRMWARE_REVISION, + FIRMWARE_SUB_REVISION, + MANUFACTURER, +) + + +class HDEntity(Entity): + """Base class for hunter douglas entities.""" + + def __init__(self, coordinator, device_info, unique_id): + """Initialize the entity.""" + super().__init__() + self._coordinator = coordinator + self._unique_id = unique_id + self._device_info = device_info + + @property + def available(self): + """Return True if entity is available.""" + return self._coordinator.last_update_success + + @property + def unique_id(self): + """Return the unique id.""" + return self._unique_id + + @property + def should_poll(self): + """Return False, updates are controlled via coordinator.""" + return False + + @property + def device_info(self): + """Return the device_info of the device.""" + firmware = self._device_info[DEVICE_FIRMWARE] + sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" + return { + "identifiers": {(DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER])}, + "connections": { + (dr.CONNECTION_NETWORK_MAC, self._device_info[DEVICE_MAC_ADDRESS]) + }, + "name": self._device_info[DEVICE_NAME], + "model": self._device_info[DEVICE_MODEL], + "sw_version": sw_version, + "manufacturer": MANUFACTURER, + } diff --git a/homeassistant/components/hunterdouglas_powerview/manifest.json b/homeassistant/components/hunterdouglas_powerview/manifest.json index 68fc6118a34..b68ec02d3f6 100644 --- a/homeassistant/components/hunterdouglas_powerview/manifest.json +++ b/homeassistant/components/hunterdouglas_powerview/manifest.json @@ -2,6 +2,12 @@ "domain": "hunterdouglas_powerview", "name": "Hunter Douglas PowerView", "documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview", - "requirements": ["aiopvapi==1.6.14"], - "codeowners": [] -} + "requirements": [ + "aiopvapi==1.6.14" + ], + "codeowners": ["@bdraco"], + "config_flow": true, + "homekit": { + "models": ["PowerView"] + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index b73ce8fd7d5..0e98ce0448d 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -2,86 +2,73 @@ import logging from typing import Any -from aiopvapi.helpers.aiorequest import AioRequest from aiopvapi.resources.scene import Scene as PvScene -from aiopvapi.rooms import Rooms -from aiopvapi.scenes import Scenes import voluptuous as vol -from homeassistant.components.scene import DOMAIN, Scene -from homeassistant.const import CONF_PLATFORM -from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.components.scene import Scene +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_HOST, CONF_PLATFORM import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import async_generate_entity_id + +from .const import ( + COORDINATOR, + DEVICE_INFO, + DOMAIN, + HUB_ADDRESS, + PV_API, + PV_ROOM_DATA, + PV_SCENE_DATA, + ROOM_NAME_UNICODE, + STATE_ATTRIBUTE_ROOM_NAME, +) +from .entity import HDEntity _LOGGER = logging.getLogger(__name__) -ENTITY_ID_FORMAT = DOMAIN + ".{}" -HUB_ADDRESS = "address" PLATFORM_SCHEMA = vol.Schema( - { - vol.Required(CONF_PLATFORM): "hunterdouglas_powerview", - vol.Required(HUB_ADDRESS): cv.string, - } + {vol.Required(CONF_PLATFORM): DOMAIN, vol.Required(HUB_ADDRESS): cv.string} ) -SCENE_DATA = "sceneData" -ROOM_DATA = "roomData" -SCENE_NAME = "name" -ROOM_NAME = "name" -SCENE_ID = "id" -ROOM_ID = "id" -ROOM_ID_IN_SCENE = "roomId" -STATE_ATTRIBUTE_ROOM_NAME = "roomName" +def setup_platform(hass, config, add_entities, discovery_info=None): + """Import platform from yaml.""" + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_HOST: config[HUB_ADDRESS]}, + ) + ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up Home Assistant scene entries.""" +async def async_setup_entry(hass, entry, async_add_entities): + """Set up powerview scene entries.""" - hub_address = config.get(HUB_ADDRESS) - websession = async_get_clientsession(hass) + pv_data = hass.data[DOMAIN][entry.entry_id] + room_data = pv_data[PV_ROOM_DATA] + scene_data = pv_data[PV_SCENE_DATA] + pv_request = pv_data[PV_API] + coordinator = pv_data[COORDINATOR] + device_info = pv_data[DEVICE_INFO] - pv_request = AioRequest(hub_address, loop=hass.loop, websession=websession) - - _scenes = await Scenes(pv_request).get_resources() - _rooms = await Rooms(pv_request).get_resources() - - if not _scenes or not _rooms: - _LOGGER.error("Unable to initialize PowerView hub: %s", hub_address) - return pvscenes = ( - PowerViewScene(hass, PvScene(_raw_scene, pv_request), _rooms) - for _raw_scene in _scenes[SCENE_DATA] + PowerViewScene( + PvScene(raw_scene, pv_request), room_data, coordinator, device_info + ) + for scene_id, raw_scene in scene_data.items() ) async_add_entities(pvscenes) -class PowerViewScene(Scene): +class PowerViewScene(HDEntity, Scene): """Representation of a Powerview scene.""" - def __init__(self, hass, scene, room_data): + def __init__(self, scene, room_data, coordinator, device_info): """Initialize the scene.""" + super().__init__(coordinator, device_info, scene.id) self._scene = scene - self.hass = hass - self._room_name = None - self._sync_room_data(room_data) - self.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, str(self._scene.id), hass=hass - ) - - def _sync_room_data(self, room_data): - """Sync room data.""" - room = next( - ( - room - for room in room_data[ROOM_DATA] - if room[ROOM_ID] == self._scene.room_id - ), - {}, - ) - - self._room_name = room.get(ROOM_NAME, "") + self._room_name = room_data.get(scene.room_id, {}).get(ROOM_NAME_UNICODE, "") @property def name(self): diff --git a/homeassistant/components/hunterdouglas_powerview/strings.json b/homeassistant/components/hunterdouglas_powerview/strings.json new file mode 100644 index 00000000000..61bc0735613 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/strings.json @@ -0,0 +1,24 @@ +{ + "title": "Hunter Douglas PowerView", + "config": { + "step": { + "user": { + "title": "Connect to the PowerView Hub", + "data": { + "host": "IP Address" + } + }, + "link": { + "title": "Connect to the PowerView Hub", + "description": "Do you want to setup {name} ({host})?" + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/en.json b/homeassistant/components/hunterdouglas_powerview/translations/en.json new file mode 100644 index 00000000000..b2e9c1f207e --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/en.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect to the PowerView Hub", + "data": { + "host": "IP Address" + } + }, + "link": { + "title": "Connect to the PowerView Hub", + "description": "Do you want to setup {name} ({host})?" + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect, please try again", + "unknown": "Unexpected error" + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 636dab4de27..6e259c2bf84 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -54,6 +54,7 @@ FLOWS = [ "homematicip_cloud", "huawei_lte", "hue", + "hunterdouglas_powerview", "iaqualink", "icloud", "ifttt", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index fa0c5ad593a..ddf9b2cdb67 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -49,6 +49,7 @@ HOMEKIT = { "Healty Home Coach": "netatmo", "LIFX": "lifx", "Netatmo Relay": "netatmo", + "PowerView": "hunterdouglas_powerview", "Presence": "netatmo", "Rachio": "rachio", "TRADFRI": "tradfri", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f02da029de7..47362e55aee 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -85,6 +85,9 @@ aiohue==2.1.0 # homeassistant.components.notion aionotion==1.1.0 +# homeassistant.components.hunterdouglas_powerview +aiopvapi==1.6.14 + # homeassistant.components.pvpc_hourly_pricing aiopvpc==1.0.2 diff --git a/tests/components/hunterdouglas_powerview/__init__.py b/tests/components/hunterdouglas_powerview/__init__.py new file mode 100644 index 00000000000..034d845b110 --- /dev/null +++ b/tests/components/hunterdouglas_powerview/__init__.py @@ -0,0 +1 @@ +"""Tests for the Hunter Douglas PowerView integration.""" diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py new file mode 100644 index 00000000000..67b2c2dc954 --- /dev/null +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -0,0 +1,220 @@ +"""Test the Logitech Harmony Hub config flow.""" +import asyncio +import json + +from asynctest import CoroutineMock, MagicMock, patch + +from homeassistant import config_entries, setup +from homeassistant.components.hunterdouglas_powerview.const import DOMAIN + +from tests.common import load_fixture + + +def _get_mock_powerview_userdata(userdata=None, get_resources=None): + mock_powerview_userdata = MagicMock() + if not userdata: + userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json")) + if get_resources: + type(mock_powerview_userdata).get_resources = CoroutineMock( + side_effect=get_resources + ) + else: + type(mock_powerview_userdata).get_resources = CoroutineMock( + return_value=userdata + ) + return mock_powerview_userdata + + +async def test_user_form(hass): + """Test we get the user 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"] == {} + + mock_powerview_userdata = _get_mock_powerview_userdata() + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ), patch( + "homeassistant.components.hunterdouglas_powerview.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.hunterdouglas_powerview.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.2.3.4"}, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "AlexanderHD" + assert result2["data"] == { + "host": "1.2.3.4", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + result3 = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result3["type"] == "form" + assert result3["errors"] == {} + + result4 = await hass.config_entries.flow.async_configure( + result3["flow_id"], {"host": "1.2.3.4"}, + ) + assert result4["type"] == "abort" + + +async def test_form_import(hass): + """Test we get the form with import source.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mock_powerview_userdata = _get_mock_powerview_userdata() + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ), patch( + "homeassistant.components.hunterdouglas_powerview.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.hunterdouglas_powerview.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={"host": "1.2.3.4"}, + ) + + assert result["type"] == "create_entry" + assert result["title"] == "AlexanderHD" + assert result["data"] == { + "host": "1.2.3.4", + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_homekit(hass): + """Test we get the form with homekit source.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mock_powerview_userdata = _get_mock_powerview_userdata() + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "homekit"}, + data={ + "host": "1.2.3.4", + "properties": {"id": "AA::BB::CC::DD::EE::FF"}, + "name": "PowerViewHub._hap._tcp.local.", + }, + ) + + assert result["type"] == "form" + assert result["step_id"] == "link" + assert result["errors"] is None + assert result["description_placeholders"] == { + "host": "1.2.3.4", + "name": "PowerViewHub", + } + + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ), patch( + "homeassistant.components.hunterdouglas_powerview.async_setup", + return_value=True, + ) as mock_setup, patch( + "homeassistant.components.hunterdouglas_powerview.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result2["type"] == "create_entry" + assert result2["title"] == "PowerViewHub" + assert result2["data"] == {"host": "1.2.3.4"} + assert result2["result"].unique_id == "ABC123" + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + result3 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "homekit"}, + data={ + "host": "1.2.3.4", + "properties": {"id": "AA::BB::CC::DD::EE::FF"}, + "name": "PowerViewHub._hap._tcp.local.", + }, + ) + assert result3["type"] == "abort" + + +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} + ) + + mock_powerview_userdata = _get_mock_powerview_userdata( + get_resources=asyncio.TimeoutError + ) + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.2.3.4"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_no_data(hass): + """Test we handle no data being returned from the hub.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerview_userdata = _get_mock_powerview_userdata(userdata={"userData": {}}) + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.2.3.4"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + +async def test_form_unknown_exception(hass): + """Test we handle unknown exception.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerview_userdata = _get_mock_powerview_userdata(userdata={"userData": {}}) + with patch( + "homeassistant.components.hunterdouglas_powerview.UserData", + return_value=mock_powerview_userdata, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.2.3.4"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} diff --git a/tests/fixtures/hunterdouglas_powerview/userdata.json b/tests/fixtures/hunterdouglas_powerview/userdata.json new file mode 100644 index 00000000000..ca5eea73f7b --- /dev/null +++ b/tests/fixtures/hunterdouglas_powerview/userdata.json @@ -0,0 +1,50 @@ +{ + "userData": { + "_id": "abc", + "color": { + "green": 0, + "blue": 255, + "brightness": 5, + "red": 0 + }, + "autoBackup": false, + "ip": "192.168.1.72", + "macAddress": "aa:bb:cc:dd:ee:ff", + "mask": "255.255.255.0", + "gateway": "192.168.1.1", + "dns": "192.168.1.3", + "firmware": { + "mainProcessor": { + "name": "PV Hub2.0", + "revision": 2, + "subRevision": 0, + "build": 1024 + }, + "radio": { + "revision": 2, + "subRevision": 0, + "build": 2610 + } + }, + "serialNumber": "ABC123", + "rfIDInt": 64789, + "rfID": "0xFD15", + "rfStatus": 0, + "brand": "HD", + "wireless": false, + "hubName": "QWxleGFuZGVySEQ=", + "localTimeDataSet": true, + "enableScheduledEvents": true, + "editingEnabled": true, + "setupCompleted": false, + "staticIp": false, + "times": { + "timezone": "America/Chicago", + "localSunriseTimeInMinutes": 0, + "localSunsetTimeInMinutes": 0, + "currentOffset": -18000 + }, + "rcUp": true, + "remoteConnectEnabled": true + } +} From 58d9c96b5f23a2c55bbbdfc81ec7fe6ab2487bd6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 29 Apr 2020 15:34:24 -0700 Subject: [PATCH 143/511] Fix Garmin Connect doing I/O in event loop (#34895) --- homeassistant/components/garmin_connect/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/garmin_connect/__init__.py b/homeassistant/components/garmin_connect/__init__.py index 8abdbbbbae9..85e8132bf02 100644 --- a/homeassistant/components/garmin_connect/__init__.py +++ b/homeassistant/components/garmin_connect/__init__.py @@ -86,6 +86,7 @@ class GarminConnectData: def __init__(self, hass, client): """Initialize.""" + self.hass = hass self.client = client self.data = None @@ -95,7 +96,9 @@ class GarminConnectData: today = date.today() try: - self.data = self.client.get_stats_and_body(today.isoformat()) + self.data = await self.hass.async_add_executor_job( + self.client.get_stats_and_body, today.isoformat() + ) except ( GarminConnectAuthenticationError, GarminConnectTooManyRequestsError, From 7d71a2c979bc9ae300e177cbedb059a6133174b1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 29 Apr 2020 15:34:55 -0700 Subject: [PATCH 144/511] Fix Toon doing I/O in event loop (#34896) --- homeassistant/components/toon/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index b078dab898d..595d3cc1ede 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -80,7 +80,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigType) -> bool: ) hass.data.setdefault(DATA_TOON_CLIENT, {})[entry.entry_id] = toon - toon_data = ToonData(hass, entry, toon) + toon_data = await hass.async_add_executor_job(ToonData, hass, entry, toon) hass.data.setdefault(DATA_TOON, {})[entry.entry_id] = toon_data async_track_time_interval(hass, toon_data.update, conf[CONF_SCAN_INTERVAL]) From 5d37eb8eeb3cbd09ad362b306036642602702d29 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 30 Apr 2020 00:03:17 +0000 Subject: [PATCH 145/511] [ci skip] Translation update --- .../components/abode/translations/ko.json | 2 +- .../components/adguard/translations/ko.json | 2 +- .../components/airvisual/translations/ko.json | 34 ++++++++++++--- .../components/airvisual/translations/pl.json | 1 + .../components/almond/translations/ko.json | 2 +- .../ambiclimate/translations/ca.json | 2 +- .../ambiclimate/translations/ko.json | 2 +- .../ambient_station/translations/ko.json | 2 +- .../components/atag/translations/ko.json | 20 +++++++++ .../components/august/translations/ko.json | 2 +- .../components/auth/translations/ko.json | 6 +-- .../components/axis/translations/ko.json | 4 +- .../coronavirus/translations/ko.json | 2 +- .../components/daikin/translations/ko.json | 2 +- .../components/deconz/translations/ko.json | 15 +++++-- .../dialogflow/translations/ko.json | 4 +- .../components/ecobee/translations/ca.json | 2 +- .../components/elgato/translations/ko.json | 2 +- .../emulated_roku/translations/ko.json | 4 +- .../components/esphome/translations/ko.json | 4 +- .../flunearyou/translations/ko.json | 2 +- .../flunearyou/translations/pl.json | 4 +- .../components/freebox/translations/ko.json | 2 +- .../components/fritzbox/translations/ca.json | 3 +- .../components/fritzbox/translations/en.json | 3 +- .../components/fritzbox/translations/es.json | 3 +- .../components/fritzbox/translations/it.json | 3 +- .../components/fritzbox/translations/ko.json | 33 +++++++++++++++ .../components/fritzbox/translations/no.json | 3 +- .../components/fritzbox/translations/pl.json | 3 +- .../components/fritzbox/translations/ru.json | 3 +- .../fritzbox/translations/zh-Hant.json | 3 +- .../components/geofency/translations/ko.json | 6 +-- .../components/glances/translations/ko.json | 2 +- .../components/gpslogger/translations/ko.json | 6 +-- .../components/griddy/translations/ko.json | 2 +- .../components/harmony/translations/ko.json | 4 +- .../homekit_controller/translations/ko.json | 8 ++-- .../homematicip_cloud/translations/ko.json | 4 +- .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/ko.json | 6 +-- .../translations/ca.json | 24 +++++++++++ .../translations/en.json | 41 +++++++++--------- .../translations/ko.json | 24 +++++++++++ .../components/ifttt/translations/ko.json | 4 +- .../components/ipp/translations/ko.json | 2 +- .../components/ipp/translations/pl.json | 2 +- .../islamic_prayer_times/translations/ca.json | 2 +- .../islamic_prayer_times/translations/en.json | 42 +++++++++---------- .../islamic_prayer_times/translations/es.json | 2 +- .../islamic_prayer_times/translations/it.json | 2 +- .../islamic_prayer_times/translations/ko.json | 23 ++++++++++ .../islamic_prayer_times/translations/lb.json | 2 +- .../islamic_prayer_times/translations/nl.json | 2 +- .../islamic_prayer_times/translations/no.json | 2 +- .../islamic_prayer_times/translations/pl.json | 2 +- .../islamic_prayer_times/translations/ru.json | 2 +- .../translations/zh-Hant.json | 2 +- .../components/konnected/translations/ko.json | 14 +++---- .../components/konnected/translations/pl.json | 5 ++- .../components/light/translations/ko.json | 2 +- .../components/light/translations/pl.json | 1 + .../components/locative/translations/ko.json | 6 +-- .../logi_circle/translations/ca.json | 2 +- .../logi_circle/translations/ko.json | 2 +- .../components/luftdaten/translations/ko.json | 2 +- .../components/mailgun/translations/ko.json | 4 +- .../components/melcloud/translations/ca.json | 2 +- .../components/mikrotik/translations/ko.json | 2 +- .../minecraft_server/translations/ko.json | 2 +- .../components/monoprice/translations/ko.json | 2 +- .../components/nest/translations/ko.json | 2 +- .../components/netatmo/translations/ko.json | 2 +- .../components/notion/translations/ko.json | 2 +- .../components/nut/translations/ko.json | 4 +- .../components/nws/translations/pl.json | 2 +- .../components/openuv/translations/ko.json | 2 +- .../components/owntracks/translations/ko.json | 2 +- .../panasonic_viera/translations/ko.json | 31 ++++++++++++++ .../panasonic_viera/translations/pl.json | 2 +- .../components/plaato/translations/ko.json | 4 +- .../components/plex/translations/ca.json | 2 +- .../components/plex/translations/ko.json | 4 +- .../components/point/translations/ko.json | 2 +- .../components/powerwall/translations/ko.json | 3 +- .../rainmachine/translations/ko.json | 2 +- .../components/ring/translations/ko.json | 2 +- .../components/samsungtv/translations/ko.json | 2 +- .../simplisafe/translations/ko.json | 5 ++- .../simplisafe/translations/no.json | 2 +- .../simplisafe/translations/pl.json | 1 + .../smartthings/translations/ca.json | 14 +++---- .../smartthings/translations/ko.json | 10 ++--- .../smartthings/translations/pl.json | 2 + .../components/solaredge/translations/ko.json | 2 +- .../components/solarlog/translations/ko.json | 2 +- .../components/spotify/translations/ko.json | 2 +- .../synology_dsm/translations/ko.json | 14 ++++++- .../components/tado/translations/pl.json | 4 ++ .../tellduslive/translations/ko.json | 4 +- .../components/timer/translations/ko.json | 2 +- .../components/toon/translations/ko.json | 4 +- .../components/traccar/translations/ko.json | 4 +- .../components/tradfri/translations/ko.json | 4 +- .../transmission/translations/ko.json | 4 +- .../components/twilio/translations/ko.json | 4 +- .../components/unifi/translations/ko.json | 3 +- .../components/unifi/translations/pl.json | 1 + .../components/upnp/translations/ko.json | 2 +- .../components/vacuum/translations/ko.json | 2 +- .../components/velbus/translations/ko.json | 2 +- .../components/vera/translations/ko.json | 2 +- .../components/vesync/translations/ko.json | 2 +- .../components/vilfo/translations/ca.json | 6 +-- .../components/vizio/translations/ca.json | 6 +-- .../components/vizio/translations/ko.json | 14 +++---- .../components/vizio/translations/no.json | 4 +- .../components/weather/translations/ko.json | 2 +- .../components/withings/translations/ko.json | 2 +- .../components/wled/translations/ko.json | 16 +++---- .../xiaomi_miio/translations/ca.json | 29 +++++++++++++ .../xiaomi_miio/translations/es.json | 29 +++++++++++++ .../xiaomi_miio/translations/it.json | 29 +++++++++++++ .../xiaomi_miio/translations/ko.json | 29 +++++++++++++ .../xiaomi_miio/translations/nl.json | 21 ++++++++++ .../xiaomi_miio/translations/no.json | 29 +++++++++++++ .../xiaomi_miio/translations/pl.json | 29 +++++++++++++ .../xiaomi_miio/translations/ru.json | 29 +++++++++++++ .../xiaomi_miio/translations/zh-Hant.json | 29 +++++++++++++ .../components/zone/translations/ko.json | 2 +- .../components/zwave/translations/ko.json | 4 +- 131 files changed, 697 insertions(+), 225 deletions(-) create mode 100644 homeassistant/components/atag/translations/ko.json create mode 100644 homeassistant/components/fritzbox/translations/ko.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/ca.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/ko.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/ko.json create mode 100644 homeassistant/components/panasonic_viera/translations/ko.json create mode 100644 homeassistant/components/xiaomi_miio/translations/ca.json create mode 100644 homeassistant/components/xiaomi_miio/translations/es.json create mode 100644 homeassistant/components/xiaomi_miio/translations/it.json create mode 100644 homeassistant/components/xiaomi_miio/translations/ko.json create mode 100644 homeassistant/components/xiaomi_miio/translations/nl.json create mode 100644 homeassistant/components/xiaomi_miio/translations/no.json create mode 100644 homeassistant/components/xiaomi_miio/translations/pl.json create mode 100644 homeassistant/components/xiaomi_miio/translations/ru.json create mode 100644 homeassistant/components/xiaomi_miio/translations/zh-Hant.json diff --git a/homeassistant/components/abode/translations/ko.json b/homeassistant/components/abode/translations/ko.json index 46363382407..0c18044045d 100644 --- a/homeassistant/components/abode/translations/ko.json +++ b/homeassistant/components/abode/translations/ko.json @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c \uc8fc\uc18c" }, - "title": "Abode \uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "Abode \uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index 1bcc60c80f0..b5b77e434ca 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -24,7 +24,7 @@ "verify_ssl": "AdGuard Home \uc740 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" }, "description": "\ubaa8\ub2c8\ud130\ub9c1 \ubc0f \uc81c\uc5b4\uac00 \uac00\ub2a5\ud558\ub3c4\ub85d AdGuard Home \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "AdGuard Home \uc5f0\uacb0" + "title": "AdGuard Home \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/airvisual/translations/ko.json b/homeassistant/components/airvisual/translations/ko.json index c28790288ab..d88e58d3ea6 100644 --- a/homeassistant/components/airvisual/translations/ko.json +++ b/homeassistant/components/airvisual/translations/ko.json @@ -1,20 +1,42 @@ { "config": { "abort": { - "already_configured": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uc88c\ud45c\uac12 \ub610\ub294 Node/Pro ID \uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "invalid_api_key": "\uc798\ubabb\ub41c API \ud0a4" + "general_error": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", + "invalid_api_key": "\uc798\ubabb\ub41c API \ud0a4", + "unable_to_connect": "AirVisual Node/Pro \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "step": { - "user": { + "geography": { "data": { "api_key": "API \ud0a4", "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" }, - "description": "\uc9c0\ub9ac\uc801 \uc704\uce58\uc5d0\uc11c \ub300\uae30\uc9c8\uc744 \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", - "title": "AirVisual \uad6c\uc131" + "description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API \ub97c \uc0ac\uc6a9\ud558\uc5ec \uc9c0\ub9ac\uc801 \uc704\uce58\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", + "title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30" + }, + "node_pro": { + "data": { + "ip_address": "\uae30\uae30 IP \uc8fc\uc18c/\ud638\uc2a4\ud2b8 \uc774\ub984", + "password": "\uae30\uae30 \ube44\ubc00\ubc88\ud638" + }, + "description": "\uc0ac\uc6a9\uc790\uc758 AirVisual \uae30\uae30\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4. \uae30\uae30\uc758 UI \uc5d0\uc11c \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "title": "AirVisual Node/Pro \uad6c\uc131\ud558\uae30" + }, + "user": { + "data": { + "api_key": "API \ud0a4", + "cloud_api": "\uc9c0\ub9ac\uc801 \uc704\uce58", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "node_pro": "AirVisual Node Pro", + "type": "\uc5f0\ub3d9 \uc720\ud615" + }, + "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 AirVisual \ub370\uc774\ud130 \uc720\ud615\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "AirVisual \uad6c\uc131\ud558\uae30" } } }, @@ -25,7 +47,7 @@ "show_on_map": "\uc9c0\ub3c4\uc5d0 \ubaa8\ub2c8\ud130\ub9c1\ub41c \uc9c0\ub9ac \uc815\ubcf4 \ud45c\uc2dc" }, "description": "AirVisual \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "AirVisual \uad6c\uc131" + "title": "AirVisual \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index 8687a2ead03..6df8d8fb623 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -15,6 +15,7 @@ "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna" }, + "description": "U\u017cyj interfejsu API chmury AirVisual do monitorowania lokalizacji geograficznej.", "title": "Konfiguracja Geography" }, "node_pro": { diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 695ca3a752c..645eaafab08 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -11,7 +11,7 @@ "title": "Hass.io \uc560\ub4dc\uc628\uc758 Almond" }, "pick_implementation": { - "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/ambiclimate/translations/ca.json b/homeassistant/components/ambiclimate/translations/ca.json index d260691eb90..14ebf481a36 100644 --- a/homeassistant/components/ambiclimate/translations/ca.json +++ b/homeassistant/components/ambiclimate/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "access_token": "S'ha produ\u00eft un error desconegut al generat un testimoni d'acc\u00e9s.", + "access_token": "S'ha produ\u00eft un error desconegut al generat un token d'acc\u00e9s.", "already_setup": "El compte d\u2019Ambi Climate est\u00e0 configurat.", "no_config": "Necessites configurar Ambiclimate abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/ambiclimate/)." }, diff --git a/homeassistant/components/ambiclimate/translations/ko.json b/homeassistant/components/ambiclimate/translations/ko.json index 311e05fa19e..2717d4c4b79 100644 --- a/homeassistant/components/ambiclimate/translations/ko.json +++ b/homeassistant/components/ambiclimate/translations/ko.json @@ -15,7 +15,7 @@ "step": { "auth": { "description": "[\ub9c1\ud06c]({authorization_url}) \ub97c \ud074\ub9ad\ud558\uc5ec Ambi Climate \uacc4\uc815\uc5d0 \ub300\ud574 \ud5c8\uc6a9 \ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694. \n(\ucf5c\ubc31 url \uc744 {cb_url} \ub85c \uad6c\uc131\ud588\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694)", - "title": "Ambi Climate \uc778\uc99d" + "title": "Ambi Climate \uc778\uc99d\ud558\uae30" } } } diff --git a/homeassistant/components/ambient_station/translations/ko.json b/homeassistant/components/ambient_station/translations/ko.json index 83d273dc4df..d4e227656c2 100644 --- a/homeassistant/components/ambient_station/translations/ko.json +++ b/homeassistant/components/ambient_station/translations/ko.json @@ -13,7 +13,7 @@ "api_key": "API \ud0a4", "app_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/atag/translations/ko.json b/homeassistant/components/atag/translations/ko.json new file mode 100644 index 00000000000..bc5c0a08f9f --- /dev/null +++ b/homeassistant/components/atag/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Home Assistant \uc5d0\ub294 \ud558\ub098\uc758 Atag \uae30\uae30\ub9cc \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4" + }, + "error": { + "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8 (10000)" + }, + "title": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index 28d6ed8842e..dce916bb788 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/translations/ko.json @@ -17,7 +17,7 @@ "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 'phone'\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" + "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" }, "validation": { "data": { diff --git a/homeassistant/components/auth/translations/ko.json b/homeassistant/components/auth/translations/ko.json index be160b185ac..563c141587f 100644 --- a/homeassistant/components/auth/translations/ko.json +++ b/homeassistant/components/auth/translations/ko.json @@ -10,11 +10,11 @@ "step": { "init": { "description": "\uc54c\ub9bc \uc11c\ube44\uc2a4 \uc911 \ud558\ub098\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694:", - "title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815" + "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:", - "title": "\uc124\uc815 \ud655\uc778" + "title": "\uc124\uc815 \ud655\uc778\ud558\uae30" } }, "title": "\uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc54c\ub9bc" @@ -26,7 +26,7 @@ "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\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.", - "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131" + "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131\ud558\uae30" } }, "title": "TOTP (\uc2dc\uac04 \uae30\ubc18 OTP)" diff --git a/homeassistant/components/axis/translations/ko.json b/homeassistant/components/axis/translations/ko.json index ae2da8858b4..b336a290f9e 100644 --- a/homeassistant/components/axis/translations/ko.json +++ b/homeassistant/components/axis/translations/ko.json @@ -8,7 +8,7 @@ }, "error": { "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.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", "device_unavailable": "\uae30\uae30\ub97c \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "faulty_credentials": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ud639\uc740 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, @@ -21,7 +21,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "Axis \uae30\uae30 \uc124\uc815" + "title": "Axis \uae30\uae30 \uc124\uc815\ud558\uae30" } } }, diff --git a/homeassistant/components/coronavirus/translations/ko.json b/homeassistant/components/coronavirus/translations/ko.json index e549a674693..65eec9e8bb7 100644 --- a/homeassistant/components/coronavirus/translations/ko.json +++ b/homeassistant/components/coronavirus/translations/ko.json @@ -8,7 +8,7 @@ "data": { "country": "\uad6d\uac00" }, - "title": "\ubaa8\ub2c8\ud130\ub9c1 \ud560 \uad6d\uac00\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694" + "title": "\ubaa8\ub2c8\ud130\ub9c1 \ud560 \uad6d\uac00\ub97c \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/daikin/translations/ko.json b/homeassistant/components/daikin/translations/ko.json index 129da9b87d0..3a45e0bd7b3 100644 --- a/homeassistant/components/daikin/translations/ko.json +++ b/homeassistant/components/daikin/translations/ko.json @@ -11,7 +11,7 @@ "host": "\ud638\uc2a4\ud2b8" }, "description": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8 \uad6c\uc131" + "title": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index e6e22abc332..3065ac361ba 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589\uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", "no_bridges": "\ubc1c\uacac\ub41c deCONZ \ube0c\ub9bf\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", "not_deconz_bridge": "deCONZ \ube0c\ub9bf\uc9c0\uac00 \uc544\ub2d9\ub2c8\ub2e4", "one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 deCONZ \uc778\uc2a4\ud134\uc2a4\ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4", @@ -18,11 +18,11 @@ "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, "init": { - "title": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uc815\uc758" + "title": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uc815\uc758\ud558\uae30" }, "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", - "title": "deCONZ\uc640 \uc5f0\uacb0" + "title": "deCONZ \uc5f0\uacb0\ud558\uae30" }, "manual_confirm": { "data": { @@ -34,7 +34,14 @@ "data": { "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" - } + }, + "title": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uad6c\uc131\ud558\uae30" + }, + "user": { + "data": { + "host": "\ubc1c\uacac\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd" + }, + "title": "deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774 \uc120\ud0dd\ud558\uae30" } } }, diff --git a/homeassistant/components/dialogflow/translations/ko.json b/homeassistant/components/dialogflow/translations/ko.json index ef49094efdb..f9ee10ceee7 100644 --- a/homeassistant/components/dialogflow/translations/ko.json +++ b/homeassistant/components/dialogflow/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Dialogflow Webhook]({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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "description": "Dialogflow \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Dialogflow Webhook \uc124\uc815" + "title": "Dialogflow \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/ecobee/translations/ca.json b/homeassistant/components/ecobee/translations/ca.json index 916c700a183..b75006483c7 100644 --- a/homeassistant/components/ecobee/translations/ca.json +++ b/homeassistant/components/ecobee/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "pin_request_failed": "Error al sol\u00b7licitar els PIN d'ecobee; verifica que la clau API \u00e9s correcta.", - "token_request_failed": "Error al sol\u00b7licitar els testimonis d'autenticaci\u00f3 d'ecobee; torna-ho a provar." + "token_request_failed": "Error al sol\u00b7licitar els tokens d'autenticaci\u00f3 d'ecobee; torna-ho a provar." }, "step": { "authorize": { diff --git a/homeassistant/components/elgato/translations/ko.json b/homeassistant/components/elgato/translations/ko.json index 50b8536ea05..05531c45011 100644 --- a/homeassistant/components/elgato/translations/ko.json +++ b/homeassistant/components/elgato/translations/ko.json @@ -15,7 +15,7 @@ "port": "\ud3ec\ud2b8 \ubc88\ud638" }, "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", - "title": "Elgato Key Light \uc5f0\uacb0" + "title": "Elgato Key Light \uc5f0\uacb0\ud558\uae30" }, "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?", diff --git a/homeassistant/components/emulated_roku/translations/ko.json b/homeassistant/components/emulated_roku/translations/ko.json index 3e1062e4201..d2f8d425a39 100644 --- a/homeassistant/components/emulated_roku/translations/ko.json +++ b/homeassistant/components/emulated_roku/translations/ko.json @@ -13,9 +13,9 @@ "name": "\uc774\ub984", "upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ud560\ub2f9 (\ucc38/\uac70\uc9d3)" }, - "title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758" + "title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758\ud558\uae30" } } }, - "title": "EmulatedRoku" + "title": "\uc5d0\ubbac\ub808\uc774\ud2b8 \ub41c Roku" } \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/ko.json b/homeassistant/components/esphome/translations/ko.json index e89f3360b32..d8546154f47 100644 --- a/homeassistant/components/esphome/translations/ko.json +++ b/homeassistant/components/esphome/translations/ko.json @@ -15,7 +15,7 @@ "password": "\ube44\ubc00\ubc88\ud638" }, "description": "{name} \uc758 \uad6c\uc131\uc5d0 \uc124\uc815\ud55c \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "\ube44\ubc00\ubc88\ud638 \uc785\ub825" + "title": "\ube44\ubc00\ubc88\ud638 \uc785\ub825\ud558\uae30" }, "discovery_confirm": { "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", @@ -27,7 +27,7 @@ "port": "\ud3ec\ud2b8" }, "description": "[ESPHome](https://esphomelib.com/) \ub178\ub4dc\uc758 \uc5f0\uacb0 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "[%key:component::esphome::title%]" + "title": "ESPHome" } } } diff --git a/homeassistant/components/flunearyou/translations/ko.json b/homeassistant/components/flunearyou/translations/ko.json index a41afe3ebc7..5c5e81476ef 100644 --- a/homeassistant/components/flunearyou/translations/ko.json +++ b/homeassistant/components/flunearyou/translations/ko.json @@ -13,7 +13,7 @@ "longitude": "\uacbd\ub3c4" }, "description": "\uc0ac\uc6a9\uc790 \uae30\ubc18 \ub370\uc774\ud130 \ubc0f CDC \ubcf4\uace0\uc11c\uc5d0\uc11c \uc88c\ud45c\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.", - "title": "Flu Near You \uad6c\uc131" + "title": "Flu Near You \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/flunearyou/translations/pl.json b/homeassistant/components/flunearyou/translations/pl.json index e674d422903..1eb13e53f9f 100644 --- a/homeassistant/components/flunearyou/translations/pl.json +++ b/homeassistant/components/flunearyou/translations/pl.json @@ -11,7 +11,9 @@ "data": { "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna" - } + }, + "description": "Monitoruj repoty oparte na u\u017cytkownikach i CDC dla pary wsp\u00f3\u0142rz\u0119dnych.", + "title": "Skonfiguruj Flu Near You" } } } diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index 9c86ce6a775..eca391cbd9f 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -11,7 +11,7 @@ "step": { "link": { "description": "\"Submit\" \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" + "title": "Freebox \ub77c\uc6b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index 44eac165110..70c2173c641 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Aquest AVM FRITZ!Box ja est\u00e0 configurat.", "already_in_progress": "La configuraci\u00f3 de l'AVM FRITZ!Box ja est\u00e0 en curs.", - "not_found": "No s'ha trobat cap AVM FRITZ!Box compatible a la xarxa." + "not_found": "No s'ha trobat cap AVM FRITZ!Box compatible a la xarxa.", + "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home." }, "error": { "auth_failed": "Nom d'usuari i/o contrasenya incorrectes." diff --git a/homeassistant/components/fritzbox/translations/en.json b/homeassistant/components/fritzbox/translations/en.json index dbfd329a128..d5d889a9083 100644 --- a/homeassistant/components/fritzbox/translations/en.json +++ b/homeassistant/components/fritzbox/translations/en.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "This AVM FRITZ!Box is already configured.", "already_in_progress": "AVM FRITZ!Box configuration is already in progress.", - "not_found": "No supported AVM FRITZ!Box found on the network." + "not_found": "No supported AVM FRITZ!Box found on the network.", + "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." }, "error": { "auth_failed": "Username and/or password are incorrect." diff --git a/homeassistant/components/fritzbox/translations/es.json b/homeassistant/components/fritzbox/translations/es.json index bb28593958c..05f956d2382 100644 --- a/homeassistant/components/fritzbox/translations/es.json +++ b/homeassistant/components/fritzbox/translations/es.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Este AVM FRITZ!Box ya est\u00e1 configurado.", "already_in_progress": "La configuraci\u00f3n del AVM FRITZ!Box ya est\u00e1 en progreso.", - "not_found": "No se encontr\u00f3 ning\u00fan AVM FRITZ!Box compatible en la red." + "not_found": "No se encontr\u00f3 ning\u00fan AVM FRITZ!Box compatible en la red.", + "not_supported": "Conectado a AVM FRITZ!Box pero no es capaz de controlar dispositivos Smart Home." }, "error": { "auth_failed": "Usuario y/o contrase\u00f1a incorrectos." diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index de0f1e0e918..f51868956bc 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Questo AVM FRITZ!Box \u00e8 gi\u00e0 configurato.", "already_in_progress": "La configurazione di AVM FRITZ!Box \u00e8 gi\u00e0 in corso.", - "not_found": "Nessun AVM FRITZ!Box supportato trovato sulla rete." + "not_found": "Nessun AVM FRITZ!Box supportato trovato sulla rete.", + "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home." }, "error": { "auth_failed": "Nome utente e/o password non sono corretti." diff --git a/homeassistant/components/fritzbox/translations/ko.json b/homeassistant/components/fritzbox/translations/ko.json new file mode 100644 index 00000000000..fc22c889695 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/ko.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 AVM FRITZ!Box \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_in_progress": "AVM FRITZ!Box \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "not_found": "\uc9c0\uc6d0\ub418\ub294 AVM FRITZ!Box \uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\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." + }, + "error": { + "auth_failed": "\uc0ac\uc6a9\uc790 \uc774\ub984 \ub610\ub294 \ube44\ubc00\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "AVM FRITZ!Box" + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8 \ub610\ub294 IP \uc8fc\uc18c", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "AVM FRITZ!Box \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "title": "AVM FRITZ!Box" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index b6a7fe0641f..85027c50af9 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Denne AVM FRITZ!Box er allerede konfigurert.", "already_in_progress": "AVM FRITZ!Box-konfigurasjon p\u00e5g\u00e5r allerede.", - "not_found": "Ingen st\u00f8ttet AVM FRITZ!Box funnet p\u00e5 nettverket." + "not_found": "Ingen st\u00f8ttet AVM FRITZ!Box funnet p\u00e5 nettverket.", + "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter." }, "error": { "auth_failed": "Brukernavn og/eller passord er feil." diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index 50ffd461d4c..8c9d58c3c42 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Ten AVM FRITZ!Box jest ju\u017c skonfigurowany.", "already_in_progress": "Konfiguracja AVM FRITZ!Box jest ju\u017c w toku.", - "not_found": "W sieci nie znaleziono obs\u0142ugiwanego urz\u0105dzenia AVM FRITZ!Box." + "not_found": "W sieci nie znaleziono obs\u0142ugiwanego urz\u0105dzenia AVM FRITZ!Box.", + "not_supported": "Po\u0142\u0105czony z AVM FRITZ! Box, ale nie jest w stanie kontrolowa\u0107 urz\u0105dze\u0144 Smart Home." }, "error": { "auth_failed": "Nazwa u\u017cytkownika i/lub has\u0142o s\u0105 nieprawid\u0142owe." diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 9f19b4d3faa..9f97ef05a34 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", - "not_found": "\u0412 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432." + "not_found": "\u0412 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.", + "not_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e." }, "error": { "auth_failed": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index 3bc06734e83..cf30a3b307f 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "\u6b64 AVM FRITZ!Box \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_in_progress": "AVM FRITZ!Box \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", - "not_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u652f\u63f4\u7684 AVM FRITZ!Box\u3002" + "not_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u652f\u63f4\u7684 AVM FRITZ!Box\u3002", + "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u8a2d\u5099\u3002" }, "error": { "auth_failed": "\u4f7f\u7528\u8005\u53ca/\u6216\u5bc6\u78bc\u932f\u8aa4\u3002" diff --git a/homeassistant/components/geofency/translations/ko.json b/homeassistant/components/geofency/translations/ko.json index f1ed98f50cd..d0d0fde2efd 100644 --- a/homeassistant/components/geofency/translations/ko.json +++ b/homeassistant/components/geofency/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Geofency \uc5d0\uc11c Webhook \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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Geofency Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Geofency Webhook \uc124\uc815" + "description": "Geofency \uc6f9 \ud6c5\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Geofency \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/glances/translations/ko.json b/homeassistant/components/glances/translations/ko.json index d0e318a0454..2cf0aa1d595 100644 --- a/homeassistant/components/glances/translations/ko.json +++ b/homeassistant/components/glances/translations/ko.json @@ -19,7 +19,7 @@ "verify_ssl": "\uc2dc\uc2a4\ud15c \uc778\uc99d \ud655\uc778", "version": "Glances API \ubc84\uc804 (2 \ub610\ub294 3)" }, - "title": "Glances \uc124\uce58" + "title": "Glances \uc124\uce58\ud558\uae30" } } }, diff --git a/homeassistant/components/gpslogger/translations/ko.json b/homeassistant/components/gpslogger/translations/ko.json index e0848849758..3c010cb65b9 100644 --- a/homeassistant/components/gpslogger/translations/ko.json +++ b/homeassistant/components/gpslogger/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 GPSLogger \uc5d0\uc11c Webhook \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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "GPSLogger Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "GPSLogger Webhook \uc124\uc815" + "description": "GPSLogger \uc6f9 \ud6c5\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "GPSLogger \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/griddy/translations/ko.json b/homeassistant/components/griddy/translations/ko.json index 9eda02551e4..a17db380aa0 100644 --- a/homeassistant/components/griddy/translations/ko.json +++ b/homeassistant/components/griddy/translations/ko.json @@ -13,7 +13,7 @@ "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" + "title": "Griddy \uc804\ub825 \uacf5\uae09 \uc9c0\uc5ed \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/harmony/translations/ko.json b/homeassistant/components/harmony/translations/ko.json index 3272c8941c7..b7d7b4e5659 100644 --- a/homeassistant/components/harmony/translations/ko.json +++ b/homeassistant/components/harmony/translations/ko.json @@ -11,14 +11,14 @@ "step": { "link": { "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Logitech Harmony Hub \uc124\uc815" + "title": "Logitech Harmony Hub \uc124\uc815\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c", "name": "Hub \uc774\ub984" }, - "title": "Logitech Harmony Hub \uc124\uc815" + "title": "Logitech Harmony Hub \uc124\uc815\ud558\uae30" } } }, diff --git a/homeassistant/components/homekit_controller/translations/ko.json b/homeassistant/components/homekit_controller/translations/ko.json index d42001f629a..6cd6c20aad0 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -3,7 +3,7 @@ "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.", "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_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.", @@ -25,16 +25,16 @@ "pairing_code": "\ud398\uc5b4\ub9c1 \ucf54\ub4dc" }, "description": "\uc774 \uc561\uc138\uc11c\ub9ac\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 HomeKit \ud398\uc5b4\ub9c1 \ucf54\ub4dc (XXX-XX-XXX \ud615\uc2dd) \ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1" + "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1\ud558\uae30" }, "user": { "data": { "device": "\uae30\uae30" }, "description": "\ud398\uc5b4\ub9c1 \ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1" + "title": "HomeKit \uc561\uc138\uc11c\ub9ac \ud398\uc5b4\ub9c1\ud558\uae30" } } }, - "title": "HomeKit \uc561\uc138\uc11c\ub9ac" + "title": "HomeKit \ucee8\ud2b8\ub864\ub7ec" } \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index 954aff55b63..630c23b6f99 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -18,11 +18,11 @@ "name": "\uc774\ub984 (\uc120\ud0dd \uc0ac\ud56d, \ubaa8\ub4e0 \uae30\uae30 \uc774\ub984\uc758 \uc811\ub450\uc5b4\ub85c \uc0ac\uc6a9)", "pin": "PIN \ucf54\ub4dc (\uc120\ud0dd\uc0ac\ud56d)" }, - "title": "HomematicIP \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc120\ud0dd" + "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 Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.\n\n![\ube0c\ub9bf\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" + "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index 28885b9435d..fdf6e03c4f7 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -24,7 +24,7 @@ "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.", - "title": "Huawei LTE \uc124\uc815" + "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } }, diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 561b3442d2e..61801ab0660 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "\ubaa8\ub4e0 \ud544\ub9bd\uc2a4 Hue \ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589\uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", "cannot_connect": "\ube0c\ub9ac\uc9c0\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "discover_timeout": "Hue \ube0c\ub9bf\uc9c0\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "no_bridges": "\ubc1c\uacac\ub41c \ud544\ub9bd\uc2a4 Hue \ube0c\ub9bf\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4", @@ -19,11 +19,11 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "title": "Hue \ube0c\ub9bf\uc9c0 \uc120\ud0dd" + "title": "Hue \ube0c\ub9bf\uc9c0 \uc120\ud0dd\ud558\uae30" }, "link": { "description": "\ube0c\ub9bf\uc9c0\uc758 \ubc84\ud2bc\uc744 \ub20c\ub7ec \ud544\ub9bd\uc2a4 Hue\ub97c Home Assistant\uc5d0 \ub4f1\ub85d\ud558\uc138\uc694.\n\n![\ube0c\ub9bf\uc9c0 \ubc84\ud2bc \uc704\uce58](/static/images/config_philips_hue.jpg)", - "title": "\ud5c8\ube0c \uc5f0\uacb0" + "title": "\ud5c8\ube0c \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/hunterdouglas_powerview/translations/ca.json b/homeassistant/components/hunterdouglas_powerview/translations/ca.json new file mode 100644 index 00000000000..de30d18bfff --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/ca.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "unknown": "Error inesperat" + }, + "step": { + "link": { + "description": "Vols configurar {name} ({host})?", + "title": "Connexi\u00f3 amb el Hub PowerView" + }, + "user": { + "data": { + "host": "Adre\u00e7a IP" + }, + "title": "Connexi\u00f3 amb el Hub PowerView" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/en.json b/homeassistant/components/hunterdouglas_powerview/translations/en.json index b2e9c1f207e..888ca60c791 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/en.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/en.json @@ -1,25 +1,24 @@ { - "config": { - "step": { - "user": { - "title": "Connect to the PowerView Hub", - "data": { - "host": "IP Address" + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "unknown": "Unexpected error" + }, + "step": { + "link": { + "description": "Do you want to setup {name} ({host})?", + "title": "Connect to the PowerView Hub" + }, + "user": { + "data": { + "host": "IP Address" + }, + "title": "Connect to the PowerView Hub" + } } - }, - "link": { - "title": "Connect to the PowerView Hub", - "description": "Do you want to setup {name} ({host})?" - } }, - "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "Device is already configured", - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" - } - } + "title": "Hunter Douglas PowerView" } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/ko.json b/homeassistant/components/hunterdouglas_powerview/translations/ko.json new file mode 100644 index 00000000000..23a02dd2a5f --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/ko.json @@ -0,0 +1,24 @@ +{ + "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. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "link": { + "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "PowerView \ud5c8\ube0c\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, + "user": { + "data": { + "host": "IP \uc8fc\uc18c" + }, + "title": "PowerView \ud5c8\ube0c\uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/ko.json b/homeassistant/components/ifttt/translations/ko.json index 30dd8b04c11..f3c0cb1062c 100644 --- a/homeassistant/components/ifttt/translations/ko.json +++ b/homeassistant/components/ifttt/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "description": "IFTTT \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "IFTTT Webhook \uc560\ud50c\ub9bf \uc124\uc815" + "title": "IFTTT \uc6f9 \ud6c5 \uc560\ud50c\ub9bf \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index a0f79d0417f..185a298d427 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -23,7 +23,7 @@ "verify_ssl": "\ud504\ub9b0\ud130\ub294 \uc62c\ubc14\ub978 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" }, "description": "\uc778\ud130\ub137 \uc778\uc1c4 \ud504\ub85c\ud1a0\ucf5c (IPP) \ub97c \ud1b5\ud574 \ud504\ub9b0\ud130\ub97c \uc124\uc815\ud558\uc5ec Home Assistant \uc640 \uc5f0\ub3d9\ud569\ub2c8\ub2e4.", - "title": "\ud504\ub9b0\ud130 \uc5f0\uacb0" + "title": "\ud504\ub9b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "zeroconf_confirm": { "description": "Home Assistant \uc5d0 `{name}` \ud504\ub9b0\ud130\ub97c \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 8bc57941cb6..ce921e48f34 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -5,7 +5,7 @@ "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z drukark\u0105.", "connection_upgrade": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z drukark\u0105 z powodu konieczno\u015bci uaktualnienia po\u0142\u0105czenia.", "ipp_error": "Wyst\u0105pi\u0142 b\u0142\u0105d IPP.", - "ipp_version_error": "Wersja IPP nie obs\u0142ugiwana przez drukark\u0119.", + "ipp_version_error": "Wersja IPP nieobs\u0142ugiwana przez drukark\u0119.", "parse_error": "Nie mo\u017cna przeanalizowa\u0107 odpowiedzi z drukarki." }, "error": { diff --git a/homeassistant/components/islamic_prayer_times/translations/ca.json b/homeassistant/components/islamic_prayer_times/translations/ca.json index f57986d2ffd..6b01d442df8 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ca.json +++ b/homeassistant/components/islamic_prayer_times/translations/ca.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "M\u00e8tode de c\u00e0lcul dels temps" + "calculation_method": "M\u00e8tode de c\u00e0lcul dels temps" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/en.json b/homeassistant/components/islamic_prayer_times/translations/en.json index 4db928c0ede..e9781c17fb1 100644 --- a/homeassistant/components/islamic_prayer_times/translations/en.json +++ b/homeassistant/components/islamic_prayer_times/translations/en.json @@ -1,23 +1,23 @@ { - "config": { - "abort": { - "one_instance_allowed": "Only a single instance is necessary." - }, - "step": { - "user": { - "description": "Do you want to set up Islamic Prayer Times?", - "title": "Set up Islamic Prayer Times" - } - } - }, - "options": { - "step": { - "init": { - "data": { - "calculation_method": "Prayer calculation method" + "config": { + "abort": { + "one_instance_allowed": "Only a single instance is necessary." + }, + "step": { + "user": { + "description": "Do you want to set up Islamic Prayer Times?", + "title": "Set up Islamic Prayer Times" + } } - } - } - }, - "title": "Islamic Prayer Times" -} + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "Prayer calculation method" + } + } + } + }, + "title": "Islamic Prayer Times" +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/es.json b/homeassistant/components/islamic_prayer_times/translations/es.json index b7eeb0db8c8..8dc6c5e5cf8 100644 --- a/homeassistant/components/islamic_prayer_times/translations/es.json +++ b/homeassistant/components/islamic_prayer_times/translations/es.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "M\u00e9todo de c\u00e1lculo de la oraci\u00f3n" + "calculation_method": "M\u00e9todo de c\u00e1lculo de la oraci\u00f3n" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/it.json b/homeassistant/components/islamic_prayer_times/translations/it.json index cd5155101eb..02f3c5df2ec 100644 --- a/homeassistant/components/islamic_prayer_times/translations/it.json +++ b/homeassistant/components/islamic_prayer_times/translations/it.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "Metodo di calcolo della preghiera" + "calculation_method": "Metodo di calcolo della preghiera" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/ko.json b/homeassistant/components/islamic_prayer_times/translations/ko.json new file mode 100644 index 00000000000..300e4661795 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/ko.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "step": { + "user": { + "description": "\uc774\uc2ac\ub78c \uae30\ub3c4 \uc2dc\uac04\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc774\uc2ac\ub78c \uae30\ub3c4 \uc2dc\uac04 \uc124\uc815\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "\uc774\uc2ac\ub78c \uae30\ub3c4 \uc2dc\uac04 \uacc4\uc0b0 \ubc29\ubc95" + } + } + } + }, + "title": "\uc774\uc2ac\ub78c \uae30\ub3c4 \uc2dc\uac04" +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/lb.json b/homeassistant/components/islamic_prayer_times/translations/lb.json index 7cdf0beb385..7ba93417436 100644 --- a/homeassistant/components/islamic_prayer_times/translations/lb.json +++ b/homeassistant/components/islamic_prayer_times/translations/lb.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "Gebieder Berechnungsmethod" + "calculation_method": "Gebieder Berechnungsmethod" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/nl.json b/homeassistant/components/islamic_prayer_times/translations/nl.json index b0cab2d938d..2f69b417998 100644 --- a/homeassistant/components/islamic_prayer_times/translations/nl.json +++ b/homeassistant/components/islamic_prayer_times/translations/nl.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "Berekeningsmethode voor het gebed" + "calculation_method": "Berekeningsmethode voor het gebed" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/no.json b/homeassistant/components/islamic_prayer_times/translations/no.json index ea5e7ae807b..59e601648ff 100644 --- a/homeassistant/components/islamic_prayer_times/translations/no.json +++ b/homeassistant/components/islamic_prayer_times/translations/no.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "Metode for beregning av b\u00f8nn" + "calculation_method": "Metode for beregning av b\u00f8nn" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/pl.json b/homeassistant/components/islamic_prayer_times/translations/pl.json index a706e6a0596..b0f87a15404 100644 --- a/homeassistant/components/islamic_prayer_times/translations/pl.json +++ b/homeassistant/components/islamic_prayer_times/translations/pl.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "Metoda kalkulacji modlitwy" + "calculation_method": "Metoda kalkulacji modlitwy" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/ru.json b/homeassistant/components/islamic_prayer_times/translations/ru.json index 95635fb6802..799c4bc5500 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ru.json +++ b/homeassistant/components/islamic_prayer_times/translations/ru.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "\u041c\u0435\u0442\u043e\u0434 \u0440\u0430\u0441\u0447\u0435\u0442\u0430" + "calculation_method": "\u041c\u0435\u0442\u043e\u0434 \u0440\u0430\u0441\u0447\u0435\u0442\u0430" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json index 568b634d903..34773b88ac0 100644 --- a/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json +++ b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json @@ -14,7 +14,7 @@ "step": { "init": { "data": { - "calc_method": "\u7948\u79b1\u8a08\u7b97\u65b9\u5f0f" + "calculation_method": "\u7948\u79b1\u8a08\u7b97\u65b9\u5f0f" } } } diff --git a/homeassistant/components/konnected/translations/ko.json b/homeassistant/components/konnected/translations/ko.json index 6945c5ae47d..572ccfe03c7 100644 --- a/homeassistant/components/konnected/translations/ko.json +++ b/homeassistant/components/konnected/translations/ko.json @@ -2,7 +2,7 @@ "config": { "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.", + "already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", "not_konn_panel": "\uc778\uc2dd\ub41c Konnected.io \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4", "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, @@ -43,7 +43,7 @@ "type": "\uc774\uc9c4 \uc13c\uc11c \uc720\ud615" }, "description": "{zone} \uc5d0 \uc5f0\uacb0\ub41c \uc774\uc9c4 \uc13c\uc11c\uc5d0 \ub300\ud55c \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "title": "\uc774\uc9c4 \uc13c\uc11c \uad6c\uc131" + "title": "\uc774\uc9c4 \uc13c\uc11c \uad6c\uc131\ud558\uae30" }, "options_digital": { "data": { @@ -52,7 +52,7 @@ "type": "\uc13c\uc11c \uc720\ud615" }, "description": "{zone} \uc5d0 \uc5f0\uacb0\ub41c \ub514\uc9c0\ud138 \uc13c\uc11c\uc5d0 \ub300\ud55c \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694", - "title": "\ub514\uc9c0\ud138 \uc13c\uc11c \uad6c\uc131" + "title": "\ub514\uc9c0\ud138 \uc13c\uc11c \uad6c\uc131\ud558\uae30" }, "options_io": { "data": { @@ -66,7 +66,7 @@ "out": "\uc678\ubd80" }, "description": "{host} \uc5d0\uc11c {model} \uc744(\ub97c) \ubc1c\uacac\ud588\uc2b5\ub2c8\ub2e4. \uc774\uc9c4 \uc13c\uc11c(\uac1c\ud3d0 \uc811\uc810), \ub514\uc9c0\ud138 \uc13c\uc11c(dht \ubc0f ds18b20) \ub610\ub294 \uc2a4\uc704\uce58\uac00 \uac00\ub2a5\ud55c \uc13c\uc11c\uc758 I/O \uc5d0 \ub530\ub77c \uc544\ub798\uc5d0\uc11c \uac01 I/O \uc758 \uae30\ubcf8 \uad6c\uc131\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ub2e4\uc74c \ub2e8\uacc4\uc5d0\uc11c \uc138\ubd80 \uc635\uc158\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "I/O \uad6c\uc131" + "title": "I/O \uad6c\uc131\ud558\uae30" }, "options_io_ext": { "data": { @@ -80,7 +80,7 @@ "out1": "\ucd9c\ub825 1" }, "description": "\uc544\ub798\uc758 \ub098\uba38\uc9c0 I/O \uad6c\uc131\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694. \ub2e4\uc74c \ub2e8\uacc4\uc5d0\uc11c \uc138\ubd80 \uc635\uc158\uc744 \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", - "title": "\ud655\uc7a5 I/O \uad6c\uc131" + "title": "\ud655\uc7a5 I/O \uad6c\uc131\ud558\uae30" }, "options_misc": { "data": { @@ -89,7 +89,7 @@ "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", - "title": "\uae30\ud0c0 \uad6c\uc131" + "title": "\uae30\ud0c0 \uc124\uc815 \uad6c\uc131\ud558\uae30" }, "options_switch": { "data": { @@ -101,7 +101,7 @@ "repeat": "\ubc18\ubcf5 \uc2dc\uac04 (-1 = \ubb34\ud55c) (\uc120\ud0dd \uc0ac\ud56d)" }, "description": "{zone} \uad6c\uc5ed\uc5d0 \ub300\ud55c \ucd9c\ub825 \uc635\uc158\uc744 \uc120\ud0dd\ud574\uc8fc\uc138\uc694: \uc0c1\ud0dc {state}", - "title": "\uc2a4\uc704\uce58 \ucd9c\ub825 \uad6c\uc131" + "title": "\uc2a4\uc704\uce58 \ucd9c\ub825 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index 860d90536da..cf4a9aec85d 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -33,6 +33,7 @@ "not_konn_panel": "Nie rozpoznano urz\u0105dzenia Konnected.io" }, "error": { + "bad_host": "Nieprawid\u0142owy adres URL hosta zast\u0119puj\u0105cego dla interfejsu API", "few": "kilka", "many": "wiele", "one": "jeden", @@ -87,7 +88,9 @@ }, "options_misc": { "data": { - "blink": "Miganie diody LED panelu podczas wysy\u0142ania zmiany stanu" + "api_host": "Zast\u0119powanie adresu URL hosta API (opcjonalnie)", + "blink": "Miganie diody LED panelu podczas wysy\u0142ania zmiany stanu", + "override_api_host": "Zast\u0105p domy\u015blny adres URL API Home Assistant'a" }, "description": "Wybierz po\u017c\u0105dane zachowanie dla swojego panelu", "title": "R\u00f3\u017cne opcje" diff --git a/homeassistant/components/light/translations/ko.json b/homeassistant/components/light/translations/ko.json index 969d9b2e4d4..33afda2d06d 100644 --- a/homeassistant/components/light/translations/ko.json +++ b/homeassistant/components/light/translations/ko.json @@ -23,5 +23,5 @@ "on": "\ucf1c\uc9d0" } }, - "title": "\uc804\ub4f1" + "title": "\uc870\uba85" } \ No newline at end of file diff --git a/homeassistant/components/light/translations/pl.json b/homeassistant/components/light/translations/pl.json index 7cbff58c44e..3bb887b8d67 100644 --- a/homeassistant/components/light/translations/pl.json +++ b/homeassistant/components/light/translations/pl.json @@ -3,6 +3,7 @@ "action_type": { "brightness_decrease": "zmniejsz jasno\u015b\u0107 {entity_name}", "brightness_increase": "zwi\u0119ksz jasno\u015b\u0107 {entity_name}", + "flash": "b\u0142y\u015bnij {entity_name}", "toggle": "prze\u0142\u0105cz {entity_name}", "turn_off": "wy\u0142\u0105cz {entity_name}", "turn_on": "w\u0142\u0105cz {entity_name}" diff --git a/homeassistant/components/locative/translations/ko.json b/homeassistant/components/locative/translations/ko.json index e2e51a3cbd0..73767678df4 100644 --- a/homeassistant/components/locative/translations/ko.json +++ b/homeassistant/components/locative/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Locative \uc571\uc5d0\uc11c Webhook \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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { - "description": "Locative Webhook \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Locative Webhook \uc124\uc815" + "description": "Locative \uc6f9 \ud6c5\uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Locative \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/logi_circle/translations/ca.json b/homeassistant/components/logi_circle/translations/ca.json index 2da6bc19d7d..0aa0db1dc9a 100644 --- a/homeassistant/components/logi_circle/translations/ca.json +++ b/homeassistant/components/logi_circle/translations/ca.json @@ -11,7 +11,7 @@ }, "error": { "auth_error": "Ha fallat l\u2019autoritzaci\u00f3 de l\u2019API.", - "auth_timeout": "L\u2019autoritzaci\u00f3 ha expirat durant l'obtenci\u00f3 del testimoni d\u2019acc\u00e9s.", + "auth_timeout": "L\u2019autoritzaci\u00f3 ha expirat durant l'obtenci\u00f3 del token d\u2019acc\u00e9s.", "follow_link": "V\u00e9s a l'enlla\u00e7 i autentica't abans de pr\u00e9mer Envia" }, "step": { diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index 5ecea7db659..cb3e2aab323 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -17,7 +17,7 @@ "step": { "auth": { "description": "\uc544\ub798 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uc5ec Logi Circle \uacc4\uc815\uc5d0 \ub300\ud574 \ub3d9\uc758 \ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.\n\n[\ub9c1\ud06c]({authorization_url})", - "title": "Logi Circle \uc778\uc99d" + "title": "Logi Circle \uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/luftdaten/translations/ko.json b/homeassistant/components/luftdaten/translations/ko.json index a5b4c5cc466..42f3d80f880 100644 --- a/homeassistant/components/luftdaten/translations/ko.json +++ b/homeassistant/components/luftdaten/translations/ko.json @@ -11,7 +11,7 @@ "show_on_map": "\uc9c0\ub3c4\uc5d0 \ud45c\uc2dc\ud558\uae30", "station_id": "Luftdaten \uc13c\uc11c ID" }, - "title": "Luftdaten \uc124\uc815" + "title": "Luftdaten \uc815\uc758\ud558\uae30" } } } diff --git a/homeassistant/components/mailgun/translations/ko.json b/homeassistant/components/mailgun/translations/ko.json index 975b0154da5..b42dbbf9f2f 100644 --- a/homeassistant/components/mailgun/translations/ko.json +++ b/homeassistant/components/mailgun/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Mailgun Webhook]({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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "description": "Mailgun \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Mailgun Webhook \uc124\uc815" + "title": "Mailgun \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/melcloud/translations/ca.json b/homeassistant/components/melcloud/translations/ca.json index f384905d1a4..2c3bec09790 100644 --- a/homeassistant/components/melcloud/translations/ca.json +++ b/homeassistant/components/melcloud/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La integraci\u00f3 MELCloud ja est\u00e0 configurada amb aquest correu electr\u00f2nic. El testimoni d'acc\u00e9s s'ha actualitzat." + "already_configured": "La integraci\u00f3 MELCloud ja est\u00e0 configurada amb aquest correu electr\u00f2nic. El token d'acc\u00e9s s'ha actualitzat." }, "error": { "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", diff --git a/homeassistant/components/mikrotik/translations/ko.json b/homeassistant/components/mikrotik/translations/ko.json index 8c73521afd5..04efde51ad4 100644 --- a/homeassistant/components/mikrotik/translations/ko.json +++ b/homeassistant/components/mikrotik/translations/ko.json @@ -18,7 +18,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "SSL \uc0ac\uc6a9" }, - "title": "Mikrotik \ub77c\uc6b0\ud130 \uc124\uc815" + "title": "Mikrotik \ub77c\uc6b0\ud130 \uc124\uc815\ud558\uae30" } } }, diff --git a/homeassistant/components/minecraft_server/translations/ko.json b/homeassistant/components/minecraft_server/translations/ko.json index 9244acb2144..30605d72936 100644 --- a/homeassistant/components/minecraft_server/translations/ko.json +++ b/homeassistant/components/minecraft_server/translations/ko.json @@ -15,7 +15,7 @@ "name": "\uc774\ub984" }, "description": "\ubaa8\ub2c8\ud130\ub9c1\uc774 \uac00\ub2a5\ud558\ub3c4\ub85d Minecraft \uc11c\ubc84 \uc778\uc2a4\ud134\uc2a4\ub97c \uc124\uc815\ud574\uc8fc\uc138\uc694.", - "title": "Minecraft \uc11c\ubc84 \uc5f0\uacb0" + "title": "Minecraft \uc11c\ubc84 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/monoprice/translations/ko.json b/homeassistant/components/monoprice/translations/ko.json index 4ba78dcfbbf..47dd50d02e3 100644 --- a/homeassistant/components/monoprice/translations/ko.json +++ b/homeassistant/components/monoprice/translations/ko.json @@ -33,7 +33,7 @@ "source_5": "\uc785\ub825 \uc18c\uc2a4 5 \uc774\ub984", "source_6": "\uc785\ub825 \uc18c\uc2a4 6 \uc774\ub984" }, - "title": "\uc785\ub825 \uc18c\uc2a4 \uad6c\uc131" + "title": "\uc785\ub825 \uc18c\uc2a4 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/nest/translations/ko.json b/homeassistant/components/nest/translations/ko.json index 08c2f54ba7a..ee56fc4f11e 100644 --- a/homeassistant/components/nest/translations/ko.json +++ b/homeassistant/components/nest/translations/ko.json @@ -25,7 +25,7 @@ "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.", - "title": "Nest \uacc4\uc815 \uc5f0\uacb0" + "title": "Nest \uacc4\uc815 \uc5f0\uacb0\ud558\uae30" } } } diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index 624298c04e5..971430b0624 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -10,7 +10,7 @@ }, "step": { "pick_implementation": { - "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/notion/translations/ko.json b/homeassistant/components/notion/translations/ko.json index 84d55f51efc..b1c6199ef66 100644 --- a/homeassistant/components/notion/translations/ko.json +++ b/homeassistant/components/notion/translations/ko.json @@ -13,7 +13,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984 / \uc774\uba54\uc77c \uc8fc\uc18c" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/nut/translations/ko.json b/homeassistant/components/nut/translations/ko.json index 6a74c3969b6..81fe8d88a90 100644 --- a/homeassistant/components/nut/translations/ko.json +++ b/homeassistant/components/nut/translations/ko.json @@ -12,14 +12,14 @@ "data": { "resources": "\ub9ac\uc18c\uc2a4" }, - "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \ub9ac\uc18c\uc2a4 \uc120\ud0dd" + "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \ub9ac\uc18c\uc2a4 \uc120\ud0dd\ud558\uae30" }, "ups": { "data": { "alias": "\ubcc4\uba85", "resources": "\ub9ac\uc18c\uc2a4" }, - "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 UPS \uc120\ud0dd" + "title": "\ubaa8\ub2c8\ud130\ub9c1\ud560 UPS \uc120\ud0dd\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/nws/translations/pl.json b/homeassistant/components/nws/translations/pl.json index 3da4d1f3ea8..7f3b23d31ff 100644 --- a/homeassistant/components/nws/translations/pl.json +++ b/homeassistant/components/nws/translations/pl.json @@ -15,7 +15,7 @@ "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "station": "Kod stacji METAR" }, - "description": "Je\u015bli nie podasz kodu stacji METAR, do znalezienia najbli\u017cszej stacji zostan\u0105 u\u017cyte szeroko\u015b\u0107 i d\u0142ugo\u015b\u0107 geograficzna.", + "description": "Je\u015bli nie podasz kodu stacji METAR, do znalezienia najbli\u017cszej stacji zostan\u0105 u\u017cyte wsp\u00f3\u0142rz\u0119dne geograficzne.", "title": "Po\u0142\u0105cz z National Weather Service" } } diff --git a/homeassistant/components/openuv/translations/ko.json b/homeassistant/components/openuv/translations/ko.json index 77f87a19e0c..1a841c87ddd 100644 --- a/homeassistant/components/openuv/translations/ko.json +++ b/homeassistant/components/openuv/translations/ko.json @@ -12,7 +12,7 @@ "latitude": "\uc704\ub3c4", "longitude": "\uacbd\ub3c4" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/owntracks/translations/ko.json b/homeassistant/components/owntracks/translations/ko.json index 72001c7de85..107e73b98a9 100644 --- a/homeassistant/components/owntracks/translations/ko.json +++ b/homeassistant/components/owntracks/translations/ko.json @@ -9,7 +9,7 @@ "step": { "user": { "description": "OwnTracks \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "OwnTracks \uc124\uc815" + "title": "OwnTracks \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/panasonic_viera/translations/ko.json b/homeassistant/components/panasonic_viera/translations/ko.json new file mode 100644 index 00000000000..3e9c948c1c5 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/ko.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 Panasonic Viera TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "not_connected": "Panasonic Viera TV \uc640\uc758 \uc6d0\uaca9 \uc5f0\uacb0\uc774 \ub04a\uc5b4\uc84c\uc2b5\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 \ub85c\uadf8\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 \ub85c\uadf8\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" + }, + "error": { + "invalid_pin_code": "\uc785\ub825\ud55c PIN \ucf54\ub4dc\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_connected": "Panasonic Viera TV \uc5d0 \uc6d0\uaca9\uc73c\ub85c \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" + }, + "step": { + "pairing": { + "data": { + "pin": "PIN" + }, + "description": "TV \uc5d0 \ud45c\uc2dc\ub41c PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "\ud398\uc5b4\ub9c1\ud558\uae30" + }, + "user": { + "data": { + "host": "IP \uc8fc\uc18c", + "name": "\uc774\ub984" + }, + "description": "Panasonic Viera TV \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694", + "title": "TV \uc124\uc815\ud558\uae30" + } + } + }, + "title": "Panasonic Viera" +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/pl.json b/homeassistant/components/panasonic_viera/translations/pl.json index 7a880c33265..f1647772c90 100644 --- a/homeassistant/components/panasonic_viera/translations/pl.json +++ b/homeassistant/components/panasonic_viera/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Ten telewizor Panasonic Viera jest ju\u017c skonfigurowany.", "not_connected": "Zdalne po\u0142\u0105czenie z telewizorem Panasonic Viera zosta\u0142o utracone. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji.", - "unknown": "Nieznany b\u0142\u0105d, sprawd\u017a logi aby uzyska\u0107 wi\u0119cej szczeg\u00f3\u0142\u00f3w" + "unknown": "Nieznany b\u0142\u0105d, sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej szczeg\u00f3\u0142\u00f3w" }, "error": { "invalid_pin_code": "Podany kod PIN jest nieprawid\u0142owy", diff --git a/homeassistant/components/plaato/translations/ko.json b/homeassistant/components/plaato/translations/ko.json index 4cb94a81a72..10694fa470c 100644 --- a/homeassistant/components/plaato/translations/ko.json +++ b/homeassistant/components/plaato/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Plaato Airlock \uc5d0\uc11c Webhook \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 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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "description": "Plaato Airlock \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Plaato Webhook \uc124\uc815" + "title": "Plaato \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index 250f1a3b2f2..83b3e7e7e55 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -6,7 +6,7 @@ "already_in_progress": "S\u2019est\u00e0 configurant Plex", "invalid_import": "La configuraci\u00f3 importada \u00e9s inv\u00e0lida", "non-interactive": "Importaci\u00f3 no interactiva", - "token_request_timeout": "S'ha acabat el temps d'espera durant l'obtenci\u00f3 del testimoni.", + "token_request_timeout": "S'ha acabat el temps d'espera durant l'obtenci\u00f3 del token.", "unknown": "Ha fallat per motiu desconegut" }, "error": { diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index 1c8e51f9f1c..39b25c2519e 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -20,11 +20,11 @@ "server": "\uc11c\ubc84" }, "description": "\uc5ec\ub7ec \uc11c\ubc84\uac00 \uc0ac\uc6a9 \uac00\ub2a5\ud569\ub2c8\ub2e4. \ud558\ub098\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694:", - "title": "Plex \uc11c\ubc84 \uc120\ud0dd" + "title": "Plex \uc11c\ubc84 \uc120\ud0dd\ud558\uae30" }, "start_website_auth": { "description": "plex.tv \uc5d0\uc11c \uc778\uc99d\uc744 \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", - "title": "Plex \uc11c\ubc84 \uc5f0\uacb0" + "title": "Plex \uc11c\ubc84 \uc5f0\uacb0\ud558\uae30" } } }, diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index a121d9bb460..b95954af261 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -17,7 +17,7 @@ "step": { "auth": { "description": "\uc544\ub798 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud558\uc5ec Minut \uacc4\uc815\uc5d0 \ub300\ud574 \ub3d9\uc758 \ud55c \ub2e4\uc74c, \ub2e4\uc2dc \ub3cc\uc544\uc640\uc11c \ud558\ub2e8\uc758 Submit \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694. \n\n[\ub9c1\ud06c] ({authorization_url})", - "title": "Point \uc778\uc99d" + "title": "Point \uc778\uc99d\ud558\uae30" }, "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/ko.json b/homeassistant/components/powerwall/translations/ko.json index 7922a11845c..9ba7004899f 100644 --- a/homeassistant/components/powerwall/translations/ko.json +++ b/homeassistant/components/powerwall/translations/ko.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + "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." }, "step": { "user": { diff --git a/homeassistant/components/rainmachine/translations/ko.json b/homeassistant/components/rainmachine/translations/ko.json index 30d7fc10979..981849cee7b 100644 --- a/homeassistant/components/rainmachine/translations/ko.json +++ b/homeassistant/components/rainmachine/translations/ko.json @@ -14,7 +14,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/ring/translations/ko.json b/homeassistant/components/ring/translations/ko.json index caf6d824d0d..c1e93133f91 100644 --- a/homeassistant/components/ring/translations/ko.json +++ b/homeassistant/components/ring/translations/ko.json @@ -19,7 +19,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "Ring \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778" + "title": "Ring \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30" } } } diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 228c8b1f71e..496dadc06ff 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\uc774 \uc0bc\uc131 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "already_in_progress": "\uc0bc\uc131 TV \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589\uc911\uc785\ub2c8\ub2e4.", + "already_in_progress": "\uc0bc\uc131 TV \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.", "not_successful": "\uc0bc\uc131 TV \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", "not_supported": "\uc774 \uc0bc\uc131 TV \ubaa8\ub378\uc740 \ud604\uc7ac \uc9c0\uc6d0\ub418\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/simplisafe/translations/ko.json b/homeassistant/components/simplisafe/translations/ko.json index 97da5ac4e8b..0b50f4ee47a 100644 --- a/homeassistant/components/simplisafe/translations/ko.json +++ b/homeassistant/components/simplisafe/translations/ko.json @@ -10,10 +10,11 @@ "step": { "user": { "data": { + "code": "\ucf54\ub4dc (Home Assistant UI \uc5d0\uc11c \uc0ac\uc6a9\ub428)", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c \uc8fc\uc18c" }, - "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4 \uc785\ub825" + "title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } }, @@ -23,7 +24,7 @@ "data": { "code": "\ucf54\ub4dc (Home Assistant UI \uc5d0\uc11c \uc0ac\uc6a9\ub428)" }, - "title": "SimpliSafe \uad6c\uc131" + "title": "SimpliSafe \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/simplisafe/translations/no.json b/homeassistant/components/simplisafe/translations/no.json index 582fe57efd9..4e732da5d48 100644 --- a/homeassistant/components/simplisafe/translations/no.json +++ b/homeassistant/components/simplisafe/translations/no.json @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "code": "Kode (brukt i home assistant ui)" + "code": "Kode (brukt i Home Assistant brukergrensesnittet)" }, "title": "Konfigurer SimpliSafe" } diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index 6bfc6ce6037..4b7b3c8b2f1 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Kod (u\u017cywany w interfejsie Home Assistant'a)", "password": "Has\u0142o", "username": "Adres e-mail" }, diff --git a/homeassistant/components/smartthings/translations/ca.json b/homeassistant/components/smartthings/translations/ca.json index 3950d5027e9..f5a7dacf01b 100644 --- a/homeassistant/components/smartthings/translations/ca.json +++ b/homeassistant/components/smartthings/translations/ca.json @@ -6,9 +6,9 @@ }, "error": { "app_setup_error": "No s'ha pogut configurar SmartApp. Siusplau, torna-ho a provar.", - "token_forbidden": "El testimoni d'autenticaci\u00f3 no t\u00e9 cont\u00e9 els apartats OAuth obligatoris.", - "token_invalid_format": "El testimoni d'autenticaci\u00f3 ha d'estar en format UID/GUID", - "token_unauthorized": "El testimoni d'autenticaci\u00f3 no \u00e9s v\u00e0lid o ja no t\u00e9 autoritzaci\u00f3.", + "token_forbidden": "El token d'autenticaci\u00f3 no t\u00e9 cont\u00e9 els apartats OAuth obligatoris.", + "token_invalid_format": "El token d'autenticaci\u00f3 ha d'estar en format UID/GUID", + "token_unauthorized": "El token d'autenticaci\u00f3 no \u00e9s v\u00e0lid o ja no est\u00e0 autoritzat.", "webhook_error": "SmartThings no ha pogut validar l'adre\u00e7a final configurada a `base_url`. Revisa els requisits del component." }, "step": { @@ -17,10 +17,10 @@ }, "pat": { "data": { - "access_token": "Testimoni d'acc\u00e9s" + "access_token": "Token d'acc\u00e9s" }, - "description": "Introdueix un [testimoni d'acc\u00e9s personal]({token_url}) de SmartThings creat a partir de les [instruccions]({component_url}). S\u2019utilitzar\u00e0 per crear la integraci\u00f3 de Home Assistant dins el teu compte de SmartThings.", - "title": "Introdueix el testimoni d'acc\u00e9s personal" + "description": "Introdueix un [token d'acc\u00e9s personal]({token_url}) d'SmartThings creat a partir de les [instruccions]({component_url}). S\u2019utilitzar\u00e0 per crear la integraci\u00f3 de Home Assistant dins el teu compte de SmartThings.", + "title": "Introdueix el token d'acc\u00e9s personal" }, "select_location": { "data": { @@ -31,7 +31,7 @@ }, "user": { "description": "SmartThings es configurar\u00e0 per a enviar actualitzacions push a Home Assistant a:\n> {webhook_url}\n\nSi no \u00e9s correcte, actualitza la teva configuraci\u00f3, reinicia Home Assistant i torna-ho a provar.", - "title": "Introdueix el testimoni d'autenticaci\u00f3 personal" + "title": "Introdueix el token d'autenticaci\u00f3 personal" } } } diff --git a/homeassistant/components/smartthings/translations/ko.json b/homeassistant/components/smartthings/translations/ko.json index 383b1a9412c..0ee28136651 100644 --- a/homeassistant/components/smartthings/translations/ko.json +++ b/homeassistant/components/smartthings/translations/ko.json @@ -1,7 +1,7 @@ { "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 \ud6c4\ud06c 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.", + "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": { @@ -9,7 +9,7 @@ "token_forbidden": "\ud1a0\ud070\uc5d0 \ud544\uc694\ud55c OAuth \ubc94\uc704\ubaa9\ub85d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", "token_invalid_format": "\ud1a0\ud070\uc740 UID/GUID \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4", "token_unauthorized": "\ud1a0\ud070\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uac70\ub098 \uc2b9\uc778\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.", - "webhook_error": "SmartThings \ub294 `base_url` \uc5d0 \uc124\uc815\ub41c \uc5d4\ub4dc\ud3ec\uc778\ud2b8\uc758 \uc720\ud6a8\uc131\uc744 \uac80\uc0ac \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc758 \uc694\uad6c \uc0ac\ud56d\uc744 \uac80\ud1a0\ud574\uc8fc\uc138\uc694." + "webhook_error": "SmartThings \uac00 \uc6f9 \ud6c5 URL \uc744 \ud655\uc778\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc778\ud130\ub137\uc5d0\uc11c \uc6f9 \ud6c5 URL \uc5d0 \uc811\uadfc\ud560 \uc218 \uc788\ub294\uc9c0 \ud655\uc778\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "authorize": { @@ -20,7 +20,7 @@ "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" + "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825\ud558\uae30" }, "select_location": { "data": { @@ -30,8 +30,8 @@ "title": "\uc704\uce58 \uc120\ud0dd\ud558\uae30" }, "user": { - "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.", - "title": "\uac1c\uc778 \uc561\uc138\uc2a4 \ud1a0\ud070 \uc785\ub825" + "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.", + "title": "\ucf5c\ubc31 URL \ud655\uc778\ud558\uae30" } } } diff --git a/homeassistant/components/smartthings/translations/pl.json b/homeassistant/components/smartthings/translations/pl.json index 517127a5091..7738d79c3e8 100644 --- a/homeassistant/components/smartthings/translations/pl.json +++ b/homeassistant/components/smartthings/translations/pl.json @@ -19,12 +19,14 @@ "data": { "access_token": "Token dost\u0119pu" }, + "description": "Wprowad\u017a [token dost\u0119pu osobistego]({token_url}) SmartThings, kt\u00f3ry zosta\u0142 utworzony zgodnie z [instrukcj\u0105]({component_url}). Umo\u017cliwi to stworzenie integracji Home Assistant w ramach Twojego konta SmartThings.", "title": "Wprowad\u017a osobisty token dost\u0119pu" }, "select_location": { "data": { "location_id": "Lokalizacja" }, + "description": "Wybierz lokalizacj\u0119 SmartThings, kt\u00f3r\u0105 chcesz doda\u0107 do Home Assistant'a. Nast\u0119pnie otwarte zostanie nowe okno i zostaniesz poproszony o zalogowanie si\u0119 i autoryzacj\u0119 instalacji integracji Home Assistant w wybranej lokalizacji.", "title": "Wybierz lokalizacj\u0119" }, "user": { diff --git a/homeassistant/components/solaredge/translations/ko.json b/homeassistant/components/solaredge/translations/ko.json index be49825c8fb..acefe89c383 100644 --- a/homeassistant/components/solaredge/translations/ko.json +++ b/homeassistant/components/solaredge/translations/ko.json @@ -13,7 +13,7 @@ "name": "\uc774 \uc124\uce58\uc758 \uc774\ub984", "site_id": "SolarEdge site-id" }, - "title": "\uc774 \uc124\uce58\uc5d0 \ub300\ud55c API \ub9e4\uac1c\ubcc0\uc218\ub97c \uc815\uc758\ud574\uc8fc\uc138\uc694" + "title": "\uc774 \uc124\uce58\uc5d0 \ub300\ud55c API \ub9e4\uac1c\ubcc0\uc218\ub97c \uc815\uc758\ud558\uae30" } } } diff --git a/homeassistant/components/solarlog/translations/ko.json b/homeassistant/components/solarlog/translations/ko.json index 058f9ac9a9c..d603517a472 100644 --- a/homeassistant/components/solarlog/translations/ko.json +++ b/homeassistant/components/solarlog/translations/ko.json @@ -13,7 +13,7 @@ "host": "Solar-Log \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c", "name": "Solar-Log \uc13c\uc11c\uc5d0 \uc0ac\uc6a9\ub420 \uc811\ub450\uc0ac" }, - "title": "Solar-Log \uc5f0\uacb0 \uc815\uc758" + "title": "Solar-Log \uc5f0\uacb0 \uc815\uc758\ud558\uae30" } } } diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index 4892d7a45c0..deb55479c1e 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -10,7 +10,7 @@ }, "step": { "pick_implementation": { - "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index d2085b7a097..d1b59ed40f1 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -4,10 +4,20 @@ "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc0ac\uc6a9\uc790 \uc774\ub984 \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" + "connection": "\ub85c\uadf8\uc778 \uc624\ub958: \ud638\uc2a4\ud2b8\ub098 \ud3ec\ud2b8 \ub610\ub294 \uc778\uc99d\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", + "login": "\ub85c\uadf8\uc778 \uc624\ub958: \uc0ac\uc6a9\uc790 \uc774\ub984 \ubc0f \ube44\ubc00\ubc88\ud638\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", + "missing_data": "\ub204\ub77d\ub41c \ub370\uc774\ud130: \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uac70\ub098 \ub2e4\ub978 \uad6c\uc131\uc744 \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694", + "otp_failed": "2\ub2e8\uacc4 \uc778\uc99d\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. \uc0c8\ub85c\uc6b4 \ud328\uc2a4 \ucf54\ub4dc\ub85c \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694", + "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \uc790\uc138\ud55c \uc815\ubcf4\ub294 \ub85c\uadf8\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" }, "flow_title": "Synology DSM {name} ({host})", "step": { + "2sa": { + "data": { + "otp_code": "\ucf54\ub4dc" + }, + "title": "Synology DSM: 2\ub2e8\uacc4 \uc778\uc99d" + }, "link": { "data": { "api_version": "DSM \ubc84\uc804", @@ -24,7 +34,7 @@ "api_version": "DSM \ubc84\uc804", "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", - "port": "\ud3ec\ud2b8", + "port": "\ud3ec\ud2b8 (\uc120\ud0dd \uc0ac\ud56d)", "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec NAS \uc5d0 \uc5f0\uacb0", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json index 65a23d58359..da2d8eec58f 100644 --- a/homeassistant/components/tado/translations/pl.json +++ b/homeassistant/components/tado/translations/pl.json @@ -22,6 +22,10 @@ "options": { "step": { "init": { + "data": { + "fallback": "W\u0142\u0105cz tryb awaryjny." + }, + "description": "Tryb rezerwowy prze\u0142\u0105czy si\u0119 na inteligentny harmonogram przy nast\u0119pnym prze\u0142\u0105czeniu z harmonogramu po r\u0119cznym dostosowaniu strefy.", "title": "Dostosuj opcje Tado" } } diff --git a/homeassistant/components/tellduslive/translations/ko.json b/homeassistant/components/tellduslive/translations/ko.json index 7851f9d64bf..fa3cb937baf 100644 --- a/homeassistant/components/tellduslive/translations/ko.json +++ b/homeassistant/components/tellduslive/translations/ko.json @@ -12,14 +12,14 @@ "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 **SUBMIT** \uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", - "title": "TelldusLive \uc778\uc99d" + "title": "TelldusLive \uc778\uc99d\ud558\uae30" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" }, "description": "\uc8c4\uc1a1\ud569\ub2c8\ub2e4. \uad00\ub828 \ub0b4\uc6a9\uc774 \uc544\uc9c1 \uc5c5\ub370\uc774\ud2b8 \ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \ucd94\ud6c4\uc5d0 \ubc18\uc601\ub420 \uc608\uc815\uc774\ub2c8 \uc870\uae08\ub9cc \uae30\ub2e4\ub824\uc8fc\uc138\uc694.", - "title": "\uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uc120\ud0dd" + "title": "\uc5d4\ub4dc\ud3ec\uc778\ud2b8 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/timer/translations/ko.json b/homeassistant/components/timer/translations/ko.json index 5350e64524e..5ad1d79ba42 100644 --- a/homeassistant/components/timer/translations/ko.json +++ b/homeassistant/components/timer/translations/ko.json @@ -3,7 +3,7 @@ "_": { "active": "\ud65c\uc131\ud654", "idle": "\ub300\uae30\uc911", - "paused": "\uc77c\uc2dc\uc911\uc9c0\ub428" + "paused": "\uc77c\uc2dc\uc911\uc9c0" } } } \ No newline at end of file diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index 6940f8ca33f..200eb2d810b 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -19,14 +19,14 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, "description": "Eneco Toon \uacc4\uc815\uc73c\ub85c \uc778\uc99d\ud574\uc8fc\uc138\uc694. (\uac1c\ubc1c\uc790 \uacc4\uc815 \uc544\ub2d8)", - "title": "Toon \uacc4\uc815 \uc5f0\uacb0" + "title": "Toon \uacc4\uc815 \uc5f0\uacb0\ud558\uae30" }, "display": { "data": { "display": "\ub514\uc2a4\ud50c\ub808\uc774 \uc120\ud0dd" }, "description": "\uc5f0\uacb0\ud560 Toon \ub514\uc2a4\ud50c\ub808\uc774\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", - "title": "\ub514\uc2a4\ud50c\ub808\uc774 \uc120\ud0dd" + "title": "\ub514\uc2a4\ud50c\ub808\uc774 \uc120\ud0dd\ud558\uae30" } } } diff --git a/homeassistant/components/traccar/translations/ko.json b/homeassistant/components/traccar/translations/ko.json index 4d4282dc4c1..f44030821fd 100644 --- a/homeassistant/components/traccar/translations/ko.json +++ b/homeassistant/components/traccar/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 Traccar \uc5d0\uc11c Webhook \uc744 \uc124\uc815\ud574\uc57c\ud569\ub2c8\ub2e4. \n\n\ub2e4\uc74c URL \uc815\ubcf4\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 URL \uc815\ubcf4\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." }, "step": { "user": { "description": "Traccar \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Traccar \uc124\uc815" + "title": "Traccar \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/tradfri/translations/ko.json b/homeassistant/components/tradfri/translations/ko.json index a7fe2522c70..0c13b888bce 100644 --- a/homeassistant/components/tradfri/translations/ko.json +++ b/homeassistant/components/tradfri/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589\uc911\uc785\ub2c8\ub2e4." + "already_in_progress": "\ube0c\ub9bf\uc9c0 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", @@ -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" + "title": "\ubcf4\uc548 \ucf54\ub4dc \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/transmission/translations/ko.json b/homeassistant/components/transmission/translations/ko.json index b82e80a342d..a82f56670b1 100644 --- a/homeassistant/components/transmission/translations/ko.json +++ b/homeassistant/components/transmission/translations/ko.json @@ -17,7 +17,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "title": "Transmission \ud074\ub77c\uc774\uc5b8\ud2b8 \uc124\uc815" + "title": "Transmission \ud074\ub77c\uc774\uc5b8\ud2b8 \uc124\uc815\ud558\uae30" } } }, @@ -27,7 +27,7 @@ "data": { "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "title": "Transmission \uc635\uc158 \uc124\uc815" + "title": "Transmission \uc635\uc158 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/twilio/translations/ko.json b/homeassistant/components/twilio/translations/ko.json index 63859a000d0..9ceca7c23dc 100644 --- a/homeassistant/components/twilio/translations/ko.json +++ b/homeassistant/components/twilio/translations/ko.json @@ -5,12 +5,12 @@ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4." }, "create_entry": { - "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\ub824\uba74 [Twilio Webhook]({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 [\uc548\ub0b4]({docs_url}) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "description": "Twilio \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Twilio Webhook \uc124\uc815" + "title": "Twilio \uc6f9 \ud6c5 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 020a5807f30..a5a617906df 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -19,7 +19,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "verify_ssl": "\uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\ub294 \ucee8\ud2b8\ub864\ub7ec" }, - "title": "UniFi \ucee8\ud2b8\ub864\ub7ec \uc124\uc815" + "title": "UniFi \ucee8\ud2b8\ub864\ub7ec \uc124\uc815\ud558\uae30" } } }, @@ -37,6 +37,7 @@ "device_tracker": { "data": { "detection_time": "\ub9c8\uc9c0\ub9c9\uc73c\ub85c \ud655\uc778\ub41c \uc2dc\uac04\ubd80\ud130 \uc678\ucd9c \uc0c1\ud0dc\ub85c \uac04\uc8fc\ub418\ub294 \uc2dc\uac04 (\ucd08)", + "ignore_wired_bug": "UniFi \uc720\uc120 \ubc84\uadf8 \ub85c\uc9c1 \ube44\ud65c\uc131\ud654", "ssid_filter": "\ubb34\uc120 \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \ucd94\uc801\ud558\ub824\uba74 SSID\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", "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/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index db35a0b3c6a..9f7b1ca58b6 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -37,6 +37,7 @@ "device_tracker": { "data": { "detection_time": "Czas w sekundach od momentu, kiedy ostatnio widziano, a\u017c do momentu, kiedy uznano go za nieobecny.", + "ignore_wired_bug": "Wy\u0142\u0105czanie logiki b\u0142\u0119d\u00f3w dla po\u0142\u0105cze\u0144 przewodowych UniFi", "ssid_filter": "Wybierz SSIDy do \u015bledzenia klient\u00f3w bezprzewodowych", "track_clients": "\u015aled\u017a klient\u00f3w sieciowych", "track_devices": "\u015aled\u017a urz\u0105dzenia sieciowe (urz\u0105dzenia Ubiquiti)", diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index d1581b026cc..bf67706706d 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -22,7 +22,7 @@ "enable_sensors": "\ud2b8\ub798\ud53d \uc13c\uc11c \ucd94\uac00", "igd": "UPnP/IGD" }, - "title": "UPnP/IGD \uc758 \uad6c\uc131 \uc635\uc158" + "title": "\uc635\uc158 \uad6c\uc131\ud558\uae30" } } } diff --git a/homeassistant/components/vacuum/translations/ko.json b/homeassistant/components/vacuum/translations/ko.json index e82b47bc5be..0e59d5157eb 100644 --- a/homeassistant/components/vacuum/translations/ko.json +++ b/homeassistant/components/vacuum/translations/ko.json @@ -21,7 +21,7 @@ "idle": "\ub300\uae30\uc911", "off": "\uaebc\uc9d0", "on": "\ucf1c\uc9d0", - "paused": "\uc77c\uc2dc\uc911\uc9c0\ub428", + "paused": "\uc77c\uc2dc\uc911\uc9c0", "returning": "\ucda9\uc804 \ubcf5\uadc0 \uc911" } }, diff --git a/homeassistant/components/velbus/translations/ko.json b/homeassistant/components/velbus/translations/ko.json index 0d8e003472a..98829920bc7 100644 --- a/homeassistant/components/velbus/translations/ko.json +++ b/homeassistant/components/velbus/translations/ko.json @@ -13,7 +13,7 @@ "name": "Velbus \uc5f0\uacb0 \uc774\ub984", "port": "\uc5f0\uacb0 \ubb38\uc790\uc5f4" }, - "title": "Velbus \uc5f0\uacb0 \uc720\ud615 \uc815\uc758" + "title": "Velbus \uc5f0\uacb0 \uc720\ud615 \uc815\uc758\ud558\uae30" } } } diff --git a/homeassistant/components/vera/translations/ko.json b/homeassistant/components/vera/translations/ko.json index 6572570916b..56aee4bd767 100644 --- a/homeassistant/components/vera/translations/ko.json +++ b/homeassistant/components/vera/translations/ko.json @@ -12,7 +12,7 @@ "vera_controller_url": "\ucee8\ud2b8\ub864\ub7ec URL" }, "description": "\uc544\ub798\uc5d0 Vera \ucee8\ud2b8\ub864\ub7ec URL \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. http://192.168.1.161:3480 \uacfc \uac19\uc740 \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4.", - "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc124\uc815" + "title": "Vera \ucee8\ud2b8\ub864\ub7ec \uc124\uc815\ud558\uae30" } } }, diff --git a/homeassistant/components/vesync/translations/ko.json b/homeassistant/components/vesync/translations/ko.json index 20672e018f7..6fee063843b 100644 --- a/homeassistant/components/vesync/translations/ko.json +++ b/homeassistant/components/vesync/translations/ko.json @@ -12,7 +12,7 @@ "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c \uc8fc\uc18c" }, - "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694" + "title": "\uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud558\uae30" } } } diff --git a/homeassistant/components/vilfo/translations/ca.json b/homeassistant/components/vilfo/translations/ca.json index 5b8c12bab6c..8bd6185c094 100644 --- a/homeassistant/components/vilfo/translations/ca.json +++ b/homeassistant/components/vilfo/translations/ca.json @@ -5,16 +5,16 @@ }, "error": { "cannot_connect": "No s'ha pogut connectar. Verifica la informaci\u00f3 proporcionada i torna-ho a provar.", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida. Comprova el testimoni d'acc\u00e9s i torna-ho a provar.", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida. Comprova el token d'acc\u00e9s i torna-ho a provar.", "unknown": "S'ha produ\u00eft un error inesperat durant la configuraci\u00f3 de la integraci\u00f3." }, "step": { "user": { "data": { - "access_token": "Testimoni d'acc\u00e9s per l'API de l'encaminador Vilfo", + "access_token": "Token d'acc\u00e9s per l'API de l'encaminador Vilfo", "host": "Nom d'amfitri\u00f3 o IP de l'encaminador" }, - "description": "Configura la integraci\u00f3 de l'encaminador Vilfo. Necessites la seva IP o nom d'amfitri\u00f3 i el testimoni d'acc\u00e9s de l'API (token). Per a m\u00e9s informaci\u00f3, visita: https://www.home-assistant.io/integrations/vilfo", + "description": "Configura la integraci\u00f3 de l'encaminador Vilfo. Necessites la seva IP o nom d'amfitri\u00f3 i el token d'acc\u00e9s de l'API. Per a m\u00e9s informaci\u00f3, visita: https://www.home-assistant.io/integrations/vilfo", "title": "Connexi\u00f3 amb l'encaminador Vilfo" } } diff --git a/homeassistant/components/vizio/translations/ca.json b/homeassistant/components/vizio/translations/ca.json index b59346bcf42..6f2b98e053f 100644 --- a/homeassistant/components/vizio/translations/ca.json +++ b/homeassistant/components/vizio/translations/ca.json @@ -23,17 +23,17 @@ "title": "Emparellament completat" }, "pairing_complete_import": { - "description": "El dispositiu Vizio SmartCast est\u00e0 connectat a Home Assistant.\n\nEl teu testimoni d'acc\u00e9s (access token) \u00e9s '**{access_token}**'.", + "description": "El dispositiu Vizio SmartCast est\u00e0 connectat a Home Assistant.\n\nEl teu token d'acc\u00e9s \u00e9s '**{access_token}**'.", "title": "Emparellament completat" }, "user": { "data": { - "access_token": "Testimoni d'acc\u00e9s", + "access_token": "Token d'acc\u00e9s", "device_class": "Tipus de dispositiu", "host": ":", "name": "Nom" }, - "description": "Nom\u00e9s es necessita testimoni d'acc\u00e9s per als televisors. Si est\u00e0s configurant un televisor i encara no tens un testimoni d'acc\u00e9s, deixa-ho en blanc per poder fer el proc\u00e9s d'emparellament.", + "description": "Nom\u00e9s es necessita el token per als televisors. Si est\u00e0s configurant un televisor i encara no tens un token d'acc\u00e9s, deixa-ho en blanc per poder fer el proc\u00e9s d'emparellament.", "title": "Configuraci\u00f3 del client de Vizio SmartCast" } } diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 16ad1884294..31067091539 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -7,8 +7,8 @@ "error": { "cant_connect": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. [\uc548\ub0b4\ub97c \ucc38\uace0] (https://www.home-assistant.io/integrations/vizio/)\ud558\uace0 \uc591\uc2dd\uc744 \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \ub2e4\uc74c\uc744 \ub2e4\uc2dc \ud655\uc778\ud574\uc8fc\uc138\uc694.\n- \uae30\uae30 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uc2b5\ub2c8\uae4c?\n- \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\uc2b5\ub2c8\uae4c?\n- \uc785\ub825\ud55c \ub0b4\uc6a9\uc774 \uc62c\ubc14\ub985\ub2c8\uae4c?", "complete_pairing failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud558\uace0 \ub2e4\uc74c \uacfc\uc815\uc744 \uc9c4\ud589\ud558\uae30 \uc804\uc5d0 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.", - "host_exists": "\uc124\uc815\ub41c \ud638\uc2a4\ud2b8\uc758 Vizio \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "name_exists": "\uc124\uc815\ub41c \uc774\ub984\uc758 Vizio \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "host_exists": "\uc124\uc815\ub41c \ud638\uc2a4\ud2b8\uc758 VIZIO \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "name_exists": "\uc124\uc815\ub41c \uc774\ub984\uc758 VIZIO \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "step": { "pair_tv": { @@ -16,14 +16,14 @@ "pin": "PIN" }, "description": "TV \uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc591\uc2dd\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 \uc644\ub8cc" + "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 TV \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 TV \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": { @@ -34,7 +34,7 @@ "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.", - "title": "Vizio SmartCast \uae30\uae30 \uc124\uc815" + "title": "VIZIO SmartCast \uae30\uae30 \uc124\uc815\ud558\uae30" } } }, @@ -47,7 +47,7 @@ "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.", - "title": "Vizo SmartCast \uc635\uc158 \uc5c5\ub370\uc774\ud2b8" + "title": "VIZIO SmartCast \uc635\uc158 \uc5c5\ub370\uc774\ud2b8\ud558\uae30" } } } diff --git a/homeassistant/components/vizio/translations/no.json b/homeassistant/components/vizio/translations/no.json index ea2b68d75fc..3479a013084 100644 --- a/homeassistant/components/vizio/translations/no.json +++ b/homeassistant/components/vizio/translations/no.json @@ -23,7 +23,7 @@ "title": "Sammenkoblingen Er Fullf\u00f8rt" }, "pairing_complete_import": { - "description": "VIZIO SmartCast TV er n\u00e5 koblet til Hjemmeassistent.\n\nTilgangstokenet er **{access_token}**.", + "description": "VIZIO SmartCast TV er n\u00e5 koblet til Home Assistant\n\nTilgangstokenet er '**{access_token}**'.", "title": "Sammenkoblingen Er Fullf\u00f8rt" }, "user": { @@ -33,7 +33,7 @@ "host": ":", "name": "Navn" }, - "description": "En tilgangstoken er bare n\u00f8dvendig for TV-er. Hvis du konfigurerer en TV og ikke har tilgangstoken enn\u00e5, m\u00e5 du la den st\u00e5 tom for \u00e5 g\u00e5 gjennom en sammenkoblingsprosess.", + "description": "En tilgangstoken er bare n\u00f8dvendig for TV-er. Hvis du konfigurerer en TV og ikke har tilgangstoken enda, m\u00e5 du la den st\u00e5 tom for \u00e5 g\u00e5 gjennom en sammenkoblingsprosess.", "title": "Konfigurer VIZIO SmartCast-enhet" } } diff --git a/homeassistant/components/weather/translations/ko.json b/homeassistant/components/weather/translations/ko.json index 7aae9117e68..6828daca9f2 100644 --- a/homeassistant/components/weather/translations/ko.json +++ b/homeassistant/components/weather/translations/ko.json @@ -3,7 +3,7 @@ "_": { "clear-night": "\ub9d1\uc74c (\ubc24)", "cloudy": "\ud750\ub9bc", - "exceptional": "\uc608\uc678\uc0ac\ud56d", + "exceptional": "\uc774\ub840\uc801\uc778", "fog": "\uc548\uac1c", "hail": "\uc6b0\ubc15", "lightning": "\ubc88\uac1c", diff --git a/homeassistant/components/withings/translations/ko.json b/homeassistant/components/withings/translations/ko.json index f15cfc1a2de..da1d3440611 100644 --- a/homeassistant/components/withings/translations/ko.json +++ b/homeassistant/components/withings/translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "pick_implementation": { - "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd" + "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" }, "profile": { "data": { diff --git a/homeassistant/components/wled/translations/ko.json b/homeassistant/components/wled/translations/ko.json index b811023e88e..7791af99c47 100644 --- a/homeassistant/components/wled/translations/ko.json +++ b/homeassistant/components/wled/translations/ko.json @@ -1,24 +1,24 @@ { "config": { "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "already_configured": "\uc774 WLED \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "connection_error": "WLED \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "connection_error": "WLED \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "\ud638\uc2a4\ud2b8 \ub610\ub294 IP \uc8fc\uc18c" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Home Assistant \uc5d0 WLED \uc5f0\ub3d9\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "WLED \uc5f0\uacb0\ud558\uae30" }, "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "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/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json new file mode 100644 index 00000000000..b80b56a5e91 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "error": { + "connect_error": "No s'ha pogut connectar, torna-ho a provar", + "no_device_selected": "No hi ha cap dispositiu seleccionat, selecciona'n un." + }, + "step": { + "gateway": { + "data": { + "host": "Adre\u00e7a IP", + "name": "Nom de la passarel\u00b7la", + "token": "Token de l'API" + }, + "description": "Necessitar\u00e0s el token de l'API, consulta les instruccions a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "title": "Connexi\u00f3 amb la passarel\u00b7la de Xiaomi" + }, + "user": { + "data": { + "gateway": "Connexi\u00f3 amb la passarel\u00b7la de Xiaomi" + }, + "description": "Selecciona a quin dispositiu vols connectar-te.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json new file mode 100644 index 00000000000..3208afe23b0 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "connect_error": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo", + "no_device_selected": "No se ha seleccionado ning\u00fan dispositivo, por favor, seleccione un dispositivo." + }, + "step": { + "gateway": { + "data": { + "host": "Direcci\u00f3n IP", + "name": "Nombre del Gateway", + "token": "Token API" + }, + "description": "Necesitar\u00e1s el Token API, consulta https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para instrucciones.", + "title": "Conectar con un Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Conectar con un Xiaomi Gateway" + }, + "description": "Selecciona a qu\u00e9 dispositivo quieres conectar.", + "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 new file mode 100644 index 00000000000..4113cd7574c --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "connect_error": "Impossibile connettersi, si prega di riprovare", + "no_device_selected": "Nessun dispositivo selezionato, selezionare un dispositivo." + }, + "step": { + "gateway": { + "data": { + "host": "Indirizzo IP", + "name": "Nome del Gateway", + "token": "Token API" + }, + "description": "Sar\u00e0 necessario il token API, consultare https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token per istruzioni.", + "title": "Connessione a un Xiaomi Gateway " + }, + "user": { + "data": { + "gateway": "Connettiti a un Xiaomi Gateway" + }, + "description": "Selezionare a quale dispositivo si desidera collegare.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json new file mode 100644 index 00000000000..b92f943aa80 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "connect_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\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." + }, + "step": { + "gateway": { + "data": { + "host": "IP \uc8fc\uc18c", + "name": "\uac8c\uc774\ud2b8\uc6e8\uc774 \uc774\ub984", + "token": "API \ud1a0\ud070" + }, + "description": "API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token \uc548\ub0b4\ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", + "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" + }, + "user": { + "data": { + "gateway": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0" + }, + "description": "\uc5f0\uacb0\ud560 \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json new file mode 100644 index 00000000000..8762baa8714 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" + }, + "step": { + "gateway": { + "data": { + "host": "IP-adres" + }, + "title": "Maak verbinding met een Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Maak verbinding met een Xiaomi Gateway" + }, + "description": "Selecteer het apparaat waarmee u verbinding wilt maken" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json new file mode 100644 index 00000000000..d8769783c82 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "connect_error": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "no_device_selected": "Ingen enhet valgt. Velg en enhet." + }, + "step": { + "gateway": { + "data": { + "host": "IP adresse", + "name": "Navnet p\u00e5 gatewayen", + "token": "API-token" + }, + "description": "Du trenger API-symbolet, se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token for instruksjoner.", + "title": "Koble til en Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Koble til en Xiaomi Gateway" + }, + "description": "Velg hvilken enhet du vil koble til.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json new file mode 100644 index 00000000000..ac88ba11644 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie." + }, + "step": { + "gateway": { + "data": { + "host": "Adres IP", + "name": "Nazwa bramki", + "token": "Klucz API" + }, + "description": "B\u0119dziesz potrzebowa\u0107 tokenu API, odwied\u017a https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token, aby uzyska\u0107 instrukcje.", + "title": "Po\u0142\u0105cz si\u0119 z bramk\u0105 Xiaomi" + }, + "user": { + "data": { + "gateway": "Po\u0142\u0105cz si\u0119 z bramk\u0105 Xiaomi" + }, + "description": "Wybierz urz\u0105dzenie, z kt\u00f3rym chcesz si\u0119 po\u0142\u0105czy\u0107.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json new file mode 100644 index 00000000000..e1293b9f0be --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "error": { + "connect_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "no_device_selected": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u043e \u0438\u0437 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432." + }, + "step": { + "gateway": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", + "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 \u0442\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.", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Xiaomi" + }, + "user": { + "data": { + "gateway": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0448\u043b\u044e\u0437\u0443 Xiaomi" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json new file mode 100644 index 00000000000..01a1be09396 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "connect_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "no_device_selected": "\u672a\u9078\u64c7\u8a2d\u5099\uff0c\u8acb\u9078\u64c7\u4e00\u9805\u8a2d\u5099\u3002" + }, + "step": { + "gateway": { + "data": { + "host": "IP \u4f4d\u5740", + "name": "\u7db2\u95dc\u540d\u7a31", + "token": "API \u5bc6\u9470" + }, + "description": "\u5c07\u9700\u8981\u8f38\u5165 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", + "title": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" + }, + "user": { + "data": { + "gateway": "\u9023\u7dda\u81f3\u5c0f\u7c73\u7db2\u95dc" + }, + "description": "\u9078\u64c7\u6240\u8981\u9023\u7dda\u7684\u8a2d\u5099\u3002", + "title": "\u5c0f\u7c73 Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zone/translations/ko.json b/homeassistant/components/zone/translations/ko.json index 421f079a67e..789ec882dc0 100644 --- a/homeassistant/components/zone/translations/ko.json +++ b/homeassistant/components/zone/translations/ko.json @@ -13,7 +13,7 @@ "passive": "\uc790\ub3d9\ud654 \uc804\uc6a9", "radius": "\ubc18\uacbd" }, - "title": "\uad6c\uc5ed \uc124\uc815" + "title": "\uad6c\uc5ed \ub9e4\uac1c \ubcc0\uc218 \uc815\uc758\ud558\uae30" } }, "title": "\uad6c\uc5ed" diff --git a/homeassistant/components/zwave/translations/ko.json b/homeassistant/components/zwave/translations/ko.json index a30e2b40b9f..8a00886ad36 100644 --- a/homeassistant/components/zwave/translations/ko.json +++ b/homeassistant/components/zwave/translations/ko.json @@ -26,8 +26,8 @@ "sleeping": "\uc808\uc804\ubaa8\ub4dc" }, "query_stage": { - "dead": "\uc751\ub2f5\uc5c6\uc74c ({query_stage})", - "initializing": "\ucd08\uae30\ud654\uc911 ({query_stage})" + "dead": "\uc751\ub2f5\uc5c6\uc74c", + "initializing": "\ucd08\uae30\ud654\uc911" } } } \ No newline at end of file From 8467b91390ad7dabf69547cfabdae68a55c68cfc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 29 Apr 2020 19:32:05 -0500 Subject: [PATCH 146/511] Fix async scene conversion in Hunter Douglas Powerview (#34899) * Fix async scene conversion in hunter douglas powerview * Update homeassistant/components/hunterdouglas_powerview/scene.py Co-Authored-By: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/components/hunterdouglas_powerview/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hunterdouglas_powerview/scene.py b/homeassistant/components/hunterdouglas_powerview/scene.py index 0e98ce0448d..89e723a02ac 100644 --- a/homeassistant/components/hunterdouglas_powerview/scene.py +++ b/homeassistant/components/hunterdouglas_powerview/scene.py @@ -30,7 +30,7 @@ PLATFORM_SCHEMA = vol.Schema( ) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Import platform from yaml.""" hass.async_create_task( From 574d8d30a7cb153234f151fe4ad0fcf30feaa226 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 02:08:40 -0500 Subject: [PATCH 147/511] Make sqlalchemy engine connect listener recorder specific (#34908) --- homeassistant/components/recorder/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index cb5d1f4499f..fcccaa2fb9f 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -9,9 +9,7 @@ import threading import time from typing import Any, Dict, Optional -from sqlalchemy import create_engine, exc, select -from sqlalchemy.engine import Engine -from sqlalchemy.event import listens_for +from sqlalchemy import create_engine, event as sqlalchemy_event, exc, select from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.pool import StaticPool import voluptuous as vol @@ -488,15 +486,13 @@ class Recorder(threading.Thread): """Ensure database is ready to fly.""" kwargs = {} - # pylint: disable=unused-variable - @listens_for(Engine, "connect") - def setup_connection(dbapi_connection, connection_record): + def setup_recorder_connection(dbapi_connection, connection_record): """Dbapi specific connection settings.""" # We do not import sqlite3 here so mysql/other # users do not have to pay for it to be loaded in # memory - if self.db_url == "sqlite://" or ":memory:" in self.db_url: + if self.db_url.startswith("sqlite://"): old_isolation = dbapi_connection.isolation_level dbapi_connection.isolation_level = None cursor = dbapi_connection.cursor() @@ -519,6 +515,9 @@ class Recorder(threading.Thread): self.engine.dispose() self.engine = create_engine(self.db_url, **kwargs) + + sqlalchemy_event.listen(self.engine, "connect", setup_recorder_connection) + Base.metadata.create_all(self.engine) self.get_session = scoped_session(sessionmaker(bind=self.engine)) From fcd58b7c9bfd304669f448bdd9e144c5751b6af7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 02:08:56 -0500 Subject: [PATCH 148/511] Avoid error when battery appears after homekit has started (#34906) --- .../components/homekit/accessories.py | 10 ++++++- tests/components/homekit/test_accessories.py | 27 ++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index a18f42e76b6..4f0a840770c 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -99,6 +99,9 @@ class HomeAccessory(Accessory): self.entity_id = entity_id self.hass = hass self.debounce = {} + self._char_battery = None + self._char_charging = None + self._char_low_battery = None self.linked_battery_sensor = self.config.get(CONF_LINKED_BATTERY_SENSOR) self.linked_battery_charging_sensor = self.config.get( CONF_LINKED_BATTERY_CHARGING_SENSOR @@ -247,6 +250,10 @@ class HomeAccessory(Accessory): Only call this function if self._support_battery_level is True. """ + if not self._char_battery: + # Battery appeared after homekit was started + return + battery_level = convert_to_float(battery_level) if battery_level is not None: if self._char_battery.value != battery_level: @@ -258,7 +265,8 @@ class HomeAccessory(Accessory): "%s: Updated battery level to %d", self.entity_id, battery_level ) - if battery_charging is None: + # Charging state can appear after homekit was started + if battery_charging is None or not self._char_charging: return hk_charging = HK_CHARGING if battery_charging else HK_NOT_CHARGING diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index becbcb2d6d4..c4b61f68833 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -363,9 +363,30 @@ async def test_missing_linked_battery_sensor(hass, hk_driver, caplog): await hass.async_block_till_done() assert not acc.linked_battery_sensor - assert not hasattr(acc, "_char_battery") - assert not hasattr(acc, "_char_low_battery") - assert not hasattr(acc, "_char_charging") + assert acc._char_battery is None + assert acc._char_low_battery is None + assert acc._char_charging is None + + +async def test_battery_appears_after_startup(hass, hk_driver, caplog): + """Test battery level appears after homekit is started.""" + entity_id = "homekit.accessory" + hass.states.async_set(entity_id, None, {}) + await hass.async_block_till_done() + + acc = HomeAccessory( + hass, hk_driver, "Accessory without battery", entity_id, 2, None + ) + acc.update_state = lambda x: None + assert acc._char_battery is None + + await acc.run_handler() + await hass.async_block_till_done() + assert acc._char_battery is None + + hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 15}) + await hass.async_block_till_done() + assert acc._char_battery is None async def test_call_service(hass, hk_driver, events): From e01ceb1a57575308ea33f51f433db8fb0b042fd8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 02:09:33 -0500 Subject: [PATCH 149/511] Fix handling homekit thermostat states (#34905) --- .../components/homekit/type_thermostats.py | 146 +++++++---- .../homekit/test_type_thermostats.py | 231 +++++++++++++++++- 2 files changed, 319 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 0a488917381..0da92ef3dba 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -117,12 +117,15 @@ class Thermostat(HomeAccessory): """Initialize a Thermostat accessory object.""" super().__init__(*args, category=CATEGORY_THERMOSTAT) self._unit = self.hass.config.units.temperature_unit + self._state_updates = 0 + self.hc_homekit_to_hass = None + self.hc_hass_to_homekit = None min_temp, max_temp = self.get_temperature_range() # Homekit only supports 10-38, overwriting - # the max to appears to work, but less than 10 causes + # the max to appears to work, but less than 0 causes # a crash on the home app - hc_min_temp = max(min_temp, HC_MIN_TEMP) + hc_min_temp = max(min_temp, 0) hc_max_temp = max_temp min_humidity = self.hass.states.get(self.entity_id).attributes.get( @@ -149,48 +152,17 @@ class Thermostat(HomeAccessory): CHAR_CURRENT_HEATING_COOLING, value=0 ) - # Target mode characteristics - hc_modes = state.attributes.get(ATTR_HVAC_MODES) - if hc_modes is None: - _LOGGER.error( - "%s: HVAC modes not yet available. Please disable auto start for homekit.", - self.entity_id, - ) - hc_modes = ( - HVAC_MODE_HEAT, - HVAC_MODE_COOL, - HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, - ) - - # Determine available modes for this entity, - # Prefer HEAT_COOL over AUTO and COOL over FAN_ONLY, DRY - # - # HEAT_COOL is preferred over auto because HomeKit Accessory Protocol describes - # heating or cooling comes on to maintain a target temp which is closest to - # the Home Assistant spec - # - # HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range - self.hc_homekit_to_hass = { - c: s - for s, c in HC_HASS_TO_HOMEKIT.items() - if ( - s in hc_modes - and not ( - (s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes) - or ( - s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY) - and HVAC_MODE_COOL in hc_modes - ) - ) - ) - } - hc_valid_values = {k: v for v, k in self.hc_homekit_to_hass.items()} - + self._configure_hvac_modes(state) + # Must set the value first as setting + # valid_values happens before setting + # the value and if 0 is not a valid + # value this will throw self.char_target_heat_cool = serv_thermostat.configure_char( - CHAR_TARGET_HEATING_COOLING, valid_values=hc_valid_values, + CHAR_TARGET_HEATING_COOLING, value=list(self.hc_homekit_to_hass)[0] + ) + self.char_target_heat_cool.override_properties( + valid_values=self.hc_hass_to_homekit ) - # Current and target temperature characteristics self.char_current_temp = serv_thermostat.configure_char( @@ -249,7 +221,7 @@ class Thermostat(HomeAccessory): CHAR_CURRENT_HUMIDITY, value=50 ) - self.update_state(state) + self._update_state(state) serv_thermostat.setter_callback = self._set_chars @@ -356,6 +328,46 @@ class Thermostat(HomeAccessory): if CHAR_TARGET_HUMIDITY in char_values: self.set_target_humidity(char_values[CHAR_TARGET_HUMIDITY]) + def _configure_hvac_modes(self, state): + """Configure target mode characteristics.""" + hc_modes = state.attributes.get(ATTR_HVAC_MODES) + if not hc_modes: + # This cannot be none OR an empty list + _LOGGER.error( + "%s: HVAC modes not yet available. Please disable auto start for homekit.", + self.entity_id, + ) + hc_modes = ( + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + ) + + # Determine available modes for this entity, + # Prefer HEAT_COOL over AUTO and COOL over FAN_ONLY, DRY + # + # HEAT_COOL is preferred over auto because HomeKit Accessory Protocol describes + # heating or cooling comes on to maintain a target temp which is closest to + # the Home Assistant spec + # + # HVAC_MODE_HEAT_COOL: The device supports heating/cooling to a range + self.hc_homekit_to_hass = { + c: s + for s, c in HC_HASS_TO_HOMEKIT.items() + if ( + s in hc_modes + and not ( + (s == HVAC_MODE_AUTO and HVAC_MODE_HEAT_COOL in hc_modes) + or ( + s in (HVAC_MODE_DRY, HVAC_MODE_FAN_ONLY) + and HVAC_MODE_COOL in hc_modes + ) + ) + ) + } + self.hc_hass_to_homekit = {k: v for v, k in self.hc_homekit_to_hass.items()} + def get_temperature_range(self): """Return min and max temperature range.""" max_temp = self.hass.states.get(self.entity_id).attributes.get(ATTR_MAX_TEMP) @@ -382,14 +394,46 @@ class Thermostat(HomeAccessory): def update_state(self, new_state): """Update thermostat state after state changed.""" + if self._state_updates < 3: + # When we get the first state updates + # we recheck valid hvac modes as the entity + # may not have been fully setup when we saw it the + # first time + original_hc_hass_to_homekit = self.hc_hass_to_homekit + self._configure_hvac_modes(new_state) + if self.hc_hass_to_homekit != original_hc_hass_to_homekit: + if self.char_target_heat_cool.value not in self.hc_homekit_to_hass: + # We must make sure the char value is + # in the new valid values before + # setting the new valid values or + # changing them with throw + self.char_target_heat_cool.set_value( + list(self.hc_homekit_to_hass)[0], should_notify=False + ) + self.char_target_heat_cool.override_properties( + valid_values=self.hc_hass_to_homekit + ) + self._state_updates += 1 + + self._update_state(new_state) + + def _update_state(self, new_state): + """Update state without rechecking the device features.""" features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) # Update target operation mode FIRST hvac_mode = new_state.state if hvac_mode and hvac_mode in HC_HASS_TO_HOMEKIT: homekit_hvac_mode = HC_HASS_TO_HOMEKIT[hvac_mode] - if self.char_target_heat_cool.value != homekit_hvac_mode: - self.char_target_heat_cool.set_value(homekit_hvac_mode) + if homekit_hvac_mode in self.hc_homekit_to_hass: + if self.char_target_heat_cool.value != homekit_hvac_mode: + self.char_target_heat_cool.set_value(homekit_hvac_mode) + else: + _LOGGER.error( + "Cannot map hvac target mode: %s to homekit as only %s modes are supported", + hvac_mode, + self.hc_homekit_to_hass, + ) # Set current operation mode for supported thermostats hvac_action = new_state.attributes.get(ATTR_HVAC_ACTION) @@ -444,13 +488,13 @@ class Thermostat(HomeAccessory): # even if the device does not support it hc_hvac_mode = self.char_target_heat_cool.value if hc_hvac_mode == HC_HEAT_COOL_HEAT: - target_temp = self._temperature_to_homekit( - new_state.attributes.get(ATTR_TARGET_TEMP_LOW) - ) + temp_low = new_state.attributes.get(ATTR_TARGET_TEMP_LOW) + if isinstance(temp_low, (int, float)): + target_temp = self._temperature_to_homekit(temp_low) elif hc_hvac_mode == HC_HEAT_COOL_COOL: - target_temp = self._temperature_to_homekit( - new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) - ) + temp_high = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH) + if isinstance(temp_high, (int, float)): + target_temp = self._temperature_to_homekit(temp_high) if target_temp and self.char_target_temp.value != target_temp: self.char_target_temp.set_value(target_temp) diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 8ee533521e8..82abed32c0e 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -43,7 +43,6 @@ from homeassistant.components.homekit.const import ( PROP_MIN_STEP, PROP_MIN_VALUE, ) -from homeassistant.components.homekit.type_thermostats import HC_MIN_TEMP from homeassistant.components.water_heater import DOMAIN as DOMAIN_WATER_HEATER from homeassistant.const import ( ATTR_ENTITY_ID, @@ -116,7 +115,7 @@ async def test_thermostat(hass, hk_driver, cls, events): assert acc.char_current_humidity is None assert acc.char_target_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_target_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_target_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_target_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -126,6 +125,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.2, ATTR_CURRENT_TEMPERATURE: 17.8, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -142,6 +149,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 23.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -158,6 +173,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 20.0, ATTR_CURRENT_TEMPERATURE: 25.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -202,6 +225,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -218,6 +249,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 25.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -234,6 +273,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 22.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -250,6 +297,14 @@ async def test_thermostat(hass, hk_driver, cls, events): ATTR_TEMPERATURE: 22.0, ATTR_CURRENT_TEMPERATURE: 22.0, ATTR_HVAC_ACTION: CURRENT_HVAC_FAN, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -369,7 +424,15 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): HVAC_MODE_OFF, { ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE - | SUPPORT_TARGET_TEMPERATURE_RANGE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -383,10 +446,10 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): assert acc.char_heating_thresh_temp.value == 19.0 assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -397,6 +460,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 20.0, ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -415,6 +486,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 24.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -433,6 +512,14 @@ async def test_thermostat_auto(hass, hk_driver, cls, events): ATTR_TARGET_TEMP_LOW: 19.0, ATTR_CURRENT_TEMPERATURE: 21.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1094,10 +1181,10 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e assert acc.char_heating_thresh_temp.value == 19.0 assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP - assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == HC_MIN_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 hass.states.async_set( @@ -1109,6 +1196,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 18.0, ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1128,6 +1223,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 24.0, ATTR_HVAC_ACTION: CURRENT_HVAC_COOL, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1147,6 +1250,14 @@ async def test_thermostat_without_target_temp_only_range(hass, hk_driver, cls, e ATTR_CURRENT_TEMPERATURE: 21.0, ATTR_HVAC_ACTION: CURRENT_HVAC_IDLE, ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [ + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + HVAC_MODE_AUTO, + ], }, ) await hass.async_block_till_done() @@ -1400,3 +1511,109 @@ async def test_water_heater_restore(hass, hk_driver, cls, events): "Heat", "Off", } + + +async def test_thermostat_with_no_modes_when_we_first_see(hass, hk_driver, cls, events): + """Test if a thermostat that is not ready when we first see it.""" + entity_id = "climate.test" + + # support_auto = True + hass.states.async_set( + entity_id, + HVAC_MODE_OFF, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [], + }, + ) + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + + assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + + assert acc.char_target_heat_cool.value == 0 + + hass.states.async_set( + entity_id, + HVAC_MODE_HEAT_COOL, + { + ATTR_TARGET_TEMP_HIGH: 22.0, + ATTR_TARGET_TEMP_LOW: 20.0, + ATTR_CURRENT_TEMPERATURE: 18.0, + ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF, HVAC_MODE_AUTO], + }, + ) + await hass.async_block_till_done() + assert acc.char_heating_thresh_temp.value == 20.0 + assert acc.char_cooling_thresh_temp.value == 22.0 + assert acc.char_current_heat_cool.value == 1 + assert acc.char_target_heat_cool.value == 3 + assert acc.char_current_temp.value == 18.0 + assert acc.char_display_units.value == 0 + + +async def test_thermostat_with_no_off_after_recheck(hass, hk_driver, cls, events): + """Test if a thermostat that is not ready when we first see it that actually does not have off.""" + entity_id = "climate.test" + + # support_auto = True + hass.states.async_set( + entity_id, + HVAC_MODE_COOL, + { + ATTR_SUPPORTED_FEATURES: SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE, + ATTR_HVAC_MODES: [], + }, + ) + await hass.async_block_till_done() + acc = cls.thermostat(hass, hk_driver, "Climate", entity_id, 1, None) + hk_driver.add_accessory(acc) + + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.char_cooling_thresh_temp.value == 23.0 + assert acc.char_heating_thresh_temp.value == 19.0 + + assert acc.char_cooling_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_cooling_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + assert acc.char_heating_thresh_temp.properties[PROP_MAX_VALUE] == DEFAULT_MAX_TEMP + assert acc.char_heating_thresh_temp.properties[PROP_MIN_VALUE] == 7.0 + assert acc.char_heating_thresh_temp.properties[PROP_MIN_STEP] == 0.1 + + assert acc.char_target_heat_cool.value == 2 + + hass.states.async_set( + entity_id, + HVAC_MODE_HEAT_COOL, + { + ATTR_TARGET_TEMP_HIGH: 22.0, + ATTR_TARGET_TEMP_LOW: 20.0, + ATTR_CURRENT_TEMPERATURE: 18.0, + ATTR_HVAC_ACTION: CURRENT_HVAC_HEAT, + ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO], + }, + ) + await hass.async_block_till_done() + assert acc.char_heating_thresh_temp.value == 20.0 + assert acc.char_cooling_thresh_temp.value == 22.0 + assert acc.char_current_heat_cool.value == 1 + assert acc.char_target_heat_cool.value == 3 + assert acc.char_current_temp.value == 18.0 + assert acc.char_display_units.value == 0 From f3c6f665af8082e0e7c09001d7aa73a381d4e6fa Mon Sep 17 00:00:00 2001 From: jjlawren Date: Thu, 30 Apr 2020 02:10:02 -0500 Subject: [PATCH 150/511] Reduce log level for WebOS connection error (#34904) --- homeassistant/components/webostv/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index f0a059fc5b8..a441a70888b 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -145,7 +145,7 @@ async def async_setup_tv_finalize(hass, config, conf, client): if client.connection is None: async_call_later(hass, 60, async_load_platforms) - _LOGGER.warning( + _LOGGER.debug( "No connection could be made with host %s, retrying in 60 seconds", conf.get(CONF_HOST), ) From b4083dc14ffaad77f55a174ad05b6355b045eaab Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 30 Apr 2020 02:21:53 -0500 Subject: [PATCH 151/511] Use entry ID when IPP printer offers no identifier (#34316) --- homeassistant/components/ipp/__init__.py | 8 ++--- homeassistant/components/ipp/sensor.py | 39 ++++++++++++++++++------ tests/components/ipp/__init__.py | 10 ++++-- tests/components/ipp/test_sensor.py | 12 ++++++++ 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/ipp/__init__.py b/homeassistant/components/ipp/__init__.py index 5979caa37db..1258e1031b4 100644 --- a/homeassistant/components/ipp/__init__.py +++ b/homeassistant/components/ipp/__init__.py @@ -128,24 +128,20 @@ class IPPEntity(Entity): self, *, entry_id: str, + device_id: str, coordinator: IPPDataUpdateCoordinator, name: str, icon: str, enabled_default: bool = True, ) -> None: """Initialize the IPP entity.""" - self._device_id = None + self._device_id = device_id self._enabled_default = enabled_default self._entry_id = entry_id self._icon = icon self._name = name self.coordinator = coordinator - if coordinator.data.info.uuid is not None: - self._device_id = coordinator.data.info.uuid - elif coordinator.data.info.serial is not None: - self._device_id = coordinator.data.info.serial - @property def name(self) -> str: """Return the name of the entity.""" diff --git a/homeassistant/components/ipp/sensor.py b/homeassistant/components/ipp/sensor.py index 5c29be09d94..bbb051d3158 100644 --- a/homeassistant/components/ipp/sensor.py +++ b/homeassistant/components/ipp/sensor.py @@ -32,13 +32,21 @@ async def async_setup_entry( """Set up IPP sensor based on a config entry.""" coordinator: IPPDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + # config flow sets this to either UUID, serial number or None + unique_id = entry.unique_id + + if unique_id is None: + unique_id = entry.entry_id + sensors = [] - sensors.append(IPPPrinterSensor(entry.entry_id, coordinator)) - sensors.append(IPPUptimeSensor(entry.entry_id, coordinator)) + sensors.append(IPPPrinterSensor(entry.entry_id, unique_id, coordinator)) + sensors.append(IPPUptimeSensor(entry.entry_id, unique_id, coordinator)) for marker_index in range(len(coordinator.data.markers)): - sensors.append(IPPMarkerSensor(entry.entry_id, coordinator, marker_index)) + sensors.append( + IPPMarkerSensor(entry.entry_id, unique_id, coordinator, marker_index) + ) async_add_entities(sensors, True) @@ -52,6 +60,7 @@ class IPPSensor(IPPEntity): coordinator: IPPDataUpdateCoordinator, enabled_default: bool = True, entry_id: str, + unique_id: str, icon: str, key: str, name: str, @@ -62,13 +71,12 @@ class IPPSensor(IPPEntity): self._key = key self._unique_id = None - if coordinator.data.info.uuid is not None: - self._unique_id = f"{coordinator.data.info.uuid}_{key}" - elif coordinator.data.info.serial is not None: - self._unique_id = f"{coordinator.data.info.serial}_{key}" + if unique_id is not None: + self._unique_id = f"{unique_id}_{key}" super().__init__( entry_id=entry_id, + device_id=unique_id, coordinator=coordinator, name=name, icon=icon, @@ -90,7 +98,11 @@ class IPPMarkerSensor(IPPSensor): """Defines an IPP marker sensor.""" def __init__( - self, entry_id: str, coordinator: IPPDataUpdateCoordinator, marker_index: int + self, + entry_id: str, + unique_id: str, + coordinator: IPPDataUpdateCoordinator, + marker_index: int, ) -> None: """Initialize IPP marker sensor.""" self.marker_index = marker_index @@ -98,6 +110,7 @@ class IPPMarkerSensor(IPPSensor): super().__init__( coordinator=coordinator, entry_id=entry_id, + unique_id=unique_id, icon="mdi:water", key=f"marker_{marker_index}", name=f"{coordinator.data.info.name} {coordinator.data.markers[marker_index].name}", @@ -133,11 +146,14 @@ class IPPMarkerSensor(IPPSensor): class IPPPrinterSensor(IPPSensor): """Defines an IPP printer sensor.""" - def __init__(self, entry_id: str, coordinator: IPPDataUpdateCoordinator) -> None: + def __init__( + self, entry_id: str, unique_id: str, coordinator: IPPDataUpdateCoordinator + ) -> None: """Initialize IPP printer sensor.""" super().__init__( coordinator=coordinator, entry_id=entry_id, + unique_id=unique_id, icon="mdi:printer", key="printer", name=coordinator.data.info.name, @@ -166,12 +182,15 @@ class IPPPrinterSensor(IPPSensor): class IPPUptimeSensor(IPPSensor): """Defines a IPP uptime sensor.""" - def __init__(self, entry_id: str, coordinator: IPPDataUpdateCoordinator) -> None: + def __init__( + self, entry_id: str, unique_id: str, coordinator: IPPDataUpdateCoordinator + ) -> None: """Initialize IPP uptime sensor.""" super().__init__( coordinator=coordinator, enabled_default=False, entry_id=entry_id, + unique_id=unique_id, icon="mdi:clock-outline", key="uptime", name=f"{coordinator.data.info.name} Uptime", diff --git a/tests/components/ipp/__init__.py b/tests/components/ipp/__init__.py index a8c79324494..f0dc45417e1 100644 --- a/tests/components/ipp/__init__.py +++ b/tests/components/ipp/__init__.py @@ -62,7 +62,11 @@ def load_fixture_binary(filename): async def init_integration( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, skip_setup: bool = False, + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + skip_setup: bool = False, + uuid: str = "cfe92100-67c4-11d4-a45f-f8d027761251", + unique_id: str = "cfe92100-67c4-11d4-a45f-f8d027761251", ) -> MockConfigEntry: """Set up the IPP integration in Home Assistant.""" fixture = "ipp/get-printer-attributes.bin" @@ -74,14 +78,14 @@ async def init_integration( entry = MockConfigEntry( domain=DOMAIN, - unique_id="cfe92100-67c4-11d4-a45f-f8d027761251", + unique_id=unique_id, data={ CONF_HOST: "192.168.1.31", CONF_PORT: 631, CONF_SSL: False, CONF_VERIFY_SSL: True, CONF_BASE_PATH: "/ipp/print", - CONF_UUID: "cfe92100-67c4-11d4-a45f-f8d027761251", + CONF_UUID: uuid, }, ) diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index b7db606d870..e6830f559c6 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -94,3 +94,15 @@ async def test_disabled_by_default_sensors( assert entry assert entry.disabled assert entry.disabled_by == "integration" + + +async def test_missing_entry_unique_id( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> 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() + + entity = registry.async_get("sensor.epson_xp_6000_series") + assert entity + assert entity.unique_id == f"{entry.entry_id}_printer" From 15b1a9ecea3e08bfc2d242930ef69d6963f32a4f Mon Sep 17 00:00:00 2001 From: clssn Date: Thu, 30 Apr 2020 14:23:30 +0200 Subject: [PATCH 152/511] Add numato integration (#33816) * Add support for Numato 32 port USB GPIO boards Included are a binary_sensor, sensor and switch component implementations. The binary_sensor interface pushes updates via registered callback functions, so no need to poll here. Unit tests are included to test against a Numato device mockup. * Refactor numato configuration due to PR finding * Resolve minor review findings * Bump numato-gpio requirement * Load numato platforms during domain setup According to review finding * Guard from platform setup without discovery_info According to review finding * Move numato API state into hass.data According to review finding. * Avoid side effects in numato entity constructors According to review finding * Keep only first line of numato module docstrings Removed reference to the documentation. Requested by reviewer. * Minor improvements inspired by review findings * Fix async tests Pytest fixture was returning from the yield too early executing teardown code during test execution. * Improve test coverage * Configure GPIO ports early Review finding * Move read_gpio callback to outside the loop Also continue on failed switch setup, resolve other minor review findings and correct some error messages * Bump numato-gpio requirement This fixes a crash during cleanup. When any device had a communication problem, its cleanup would raise an exception which was not handled, fell through to the caller and prevented the remaining devices from being cleaned up. * Call services directly Define local helper functions for better readability. Resolves a review finding. * Assert something in every test So not only coverage is satisfied but things are actually tested to be in the expected state. Resolves a review finding. * Clarify scope of notification tests Make unit test for hass NumatoAPI independent of Home Assistant (very basic test of notifications). Improve the regular operations test for notifications. * Test for hass.states after operating switches Resolves a review finding. * Check for wrong port directions * WIP: Split numato tests to multiple files test_hass_binary_sensor_notification still fails. * Remove pytest asyncio decorator Apears to be redundant. Resolves a review finding. * Call switch services directly. Resolves a review finding. * Remove obsolete inline pylint config Co-Authored-By: Martin Hjelmare * Improve the numato_gpio module mockup Resolves a review finding. * Remove needless explicit conversions to str Resolves review findings. * Test setup of binary_sensor callbacks * Fix test_hass_binary_sensor_notification * Add forgotten await Review finding. Co-authored-by: Martin Hjelmare --- CODEOWNERS | 1 + homeassistant/components/numato/__init__.py | 248 ++++++++++++++++++ .../components/numato/binary_sensor.py | 120 +++++++++ homeassistant/components/numato/manifest.json | 8 + homeassistant/components/numato/sensor.py | 123 +++++++++ homeassistant/components/numato/switch.py | 108 ++++++++ requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/numato/__init__.py | 1 + tests/components/numato/common.py | 49 ++++ tests/components/numato/conftest.py | 28 ++ tests/components/numato/numato_mock.py | 68 +++++ tests/components/numato/test_binary_sensor.py | 62 +++++ tests/components/numato/test_init.py | 161 ++++++++++++ tests/components/numato/test_sensor.py | 38 +++ tests/components/numato/test_switch.py | 114 ++++++++ 16 files changed, 1135 insertions(+) create mode 100644 homeassistant/components/numato/__init__.py create mode 100644 homeassistant/components/numato/binary_sensor.py create mode 100644 homeassistant/components/numato/manifest.json create mode 100644 homeassistant/components/numato/sensor.py create mode 100644 homeassistant/components/numato/switch.py create mode 100644 tests/components/numato/__init__.py create mode 100644 tests/components/numato/common.py create mode 100644 tests/components/numato/conftest.py create mode 100644 tests/components/numato/numato_mock.py create mode 100644 tests/components/numato/test_binary_sensor.py create mode 100644 tests/components/numato/test_init.py create mode 100644 tests/components/numato/test_sensor.py create mode 100644 tests/components/numato/test_switch.py diff --git a/CODEOWNERS b/CODEOWNERS index 51ce87e702f..43959f67c30 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -267,6 +267,7 @@ homeassistant/components/nsw_fuel_station/* @nickw444 homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte homeassistant/components/nuheat/* @bdraco homeassistant/components/nuki/* @pvizeli +homeassistant/components/numato/* @clssn homeassistant/components/nut/* @bdraco homeassistant/components/nws/* @MatthewFlamm homeassistant/components/nzbget/* @chriscla diff --git a/homeassistant/components/numato/__init__.py b/homeassistant/components/numato/__init__.py new file mode 100644 index 00000000000..e5eeaa31846 --- /dev/null +++ b/homeassistant/components/numato/__init__.py @@ -0,0 +1,248 @@ +"""Support for controlling GPIO pins of a Numato Labs USB GPIO expander.""" +import logging + +import numato_gpio as gpio +import voluptuous as vol + +from homeassistant.const import ( + CONF_BINARY_SENSORS, + CONF_ID, + CONF_NAME, + CONF_SENSORS, + CONF_SWITCHES, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP, +) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "numato" + +CONF_INVERT_LOGIC = "invert_logic" +CONF_DISCOVER = "discover" +CONF_DEVICES = "devices" +CONF_DEVICE_ID = "id" +CONF_PORTS = "ports" +CONF_SRC_RANGE = "source_range" +CONF_DST_RANGE = "destination_range" +CONF_DST_UNIT = "unit" +DEFAULT_INVERT_LOGIC = False +DEFAULT_SRC_RANGE = [0, 1024] +DEFAULT_DST_RANGE = [0.0, 100.0] +DEFAULT_UNIT = "%" +DEFAULT_DEV = [f"/dev/ttyACM{i}" for i in range(10)] + +PORT_RANGE = range(1, 8) # ports 0-7 are ADC capable + +DATA_PORTS_IN_USE = "ports_in_use" +DATA_API = "api" + + +def int_range(rng): + """Validate the input array to describe a range by two integers.""" + if not (isinstance(rng[0], int) and isinstance(rng[1], int)): + raise vol.Invalid(f"Only integers are allowed: {rng}") + if len(rng) != 2: + raise vol.Invalid(f"Only two numbers allowed in a range: {rng}") + if rng[0] > rng[1]: + raise vol.Invalid(f"Lower range bound must come first: {rng}") + return rng + + +def float_range(rng): + """Validate the input array to describe a range by two floats.""" + try: + coe = vol.Coerce(float) + coe(rng[0]) + coe(rng[1]) + except vol.CoerceInvalid: + raise vol.Invalid(f"Only int or float values are allowed: {rng}") + if len(rng) != 2: + raise vol.Invalid(f"Only two numbers allowed in a range: {rng}") + if rng[0] > rng[1]: + raise vol.Invalid(f"Lower range bound must come first: {rng}") + return rng + + +def adc_port_number(num): + """Validate input number to be in the range of ADC enabled ports.""" + try: + num = int(num) + except (ValueError): + raise vol.Invalid(f"Port numbers must be integers: {num}") + if num not in range(1, 8): + raise vol.Invalid(f"Only port numbers from 1 to 7 are ADC capable: {num}") + return num + + +ADC_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SRC_RANGE, default=DEFAULT_SRC_RANGE): int_range, + vol.Optional(CONF_DST_RANGE, default=DEFAULT_DST_RANGE): float_range, + vol.Optional(CONF_DST_UNIT, default=DEFAULT_UNIT): cv.string, + } +) + +PORTS_SCHEMA = vol.Schema({cv.positive_int: cv.string}) + +IO_PORTS_SCHEMA = vol.Schema( + { + vol.Required(CONF_PORTS): PORTS_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + } +) + +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_ID): cv.positive_int, + CONF_BINARY_SENSORS: IO_PORTS_SCHEMA, + CONF_SWITCHES: IO_PORTS_SCHEMA, + CONF_SENSORS: {CONF_PORTS: {adc_port_number: ADC_SCHEMA}}, + } +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: { + CONF_DEVICES: vol.All(cv.ensure_list, [DEVICE_SCHEMA]), + vol.Optional(CONF_DISCOVER, default=DEFAULT_DEV): vol.All( + cv.ensure_list, [cv.string] + ), + }, + }, + extra=vol.ALLOW_EXTRA, +) + + +def setup(hass, config): + """Initialize the numato integration. + + Discovers available Numato devices and loads the binary_sensor, sensor and + switch platforms. + + Returns False on error during device discovery (e.g. duplicate ID), + otherwise returns True. + + No exceptions should occur, since the platforms are initialized on a best + effort basis, which means, errors are handled locally. + """ + hass.data[DOMAIN] = config[DOMAIN] + + try: + gpio.discover(config[DOMAIN][CONF_DISCOVER]) + except gpio.NumatoGpioError as err: + _LOGGER.info("Error discovering Numato devices: %s", err) + gpio.cleanup() + return False + + _LOGGER.info( + "Initializing Numato 32 port USB GPIO expanders with IDs: %s", + ", ".join(str(d) for d in gpio.devices), + ) + + hass.data[DOMAIN][DATA_API] = NumatoAPI() + + def cleanup_gpio(event): + """Stuff to do before stopping.""" + _LOGGER.debug("Clean up Numato GPIO") + gpio.cleanup() + if DATA_API in hass.data[DOMAIN]: + hass.data[DOMAIN][DATA_API].ports_registered.clear() + + def prepare_gpio(event): + """Stuff to do when home assistant starts.""" + _LOGGER.debug("Setup cleanup at stop for Numato GPIO") + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) + + load_platform(hass, "binary_sensor", DOMAIN, {}, config) + load_platform(hass, "sensor", DOMAIN, {}, config) + load_platform(hass, "switch", DOMAIN, {}, config) + return True + + +# pylint: disable=no-self-use +class NumatoAPI: + """Home-Assistant specific API for numato device access.""" + + def __init__(self): + """Initialize API state.""" + self.ports_registered = dict() + + def check_port_free(self, device_id, port, direction): + """Check whether a port is still free set up. + + Fail with exception if it has already been registered. + """ + if (device_id, port) not in self.ports_registered: + self.ports_registered[(device_id, port)] = direction + else: + raise gpio.NumatoGpioError( + "Device {} port {} already in use as {}.".format( + device_id, + port, + "input" + if self.ports_registered[(device_id, port)] == gpio.IN + else "output", + ) + ) + + def check_device_id(self, device_id): + """Check whether a device has been discovered. + + Fail with exception. + """ + if device_id not in gpio.devices: + raise gpio.NumatoGpioError(f"Device {device_id} not available.") + + def check_port(self, device_id, port, direction): + """Raise an error if the port setup doesn't match the direction.""" + self.check_device_id(device_id) + if (device_id, port) not in self.ports_registered: + raise gpio.NumatoGpioError( + f"Port {port} is not set up for numato device {device_id}." + ) + msg = { + gpio.OUT: f"Trying to write to device {device_id} port {port} set up as input.", + gpio.IN: f"Trying to read from device {device_id} port {port} set up as output.", + } + if self.ports_registered[(device_id, port)] != direction: + raise gpio.NumatoGpioError(msg[direction]) + + def setup_output(self, device_id, port): + """Set up a GPIO as output.""" + self.check_device_id(device_id) + self.check_port_free(device_id, port, gpio.OUT) + gpio.devices[device_id].setup(port, gpio.OUT) + + def setup_input(self, device_id, port): + """Set up a GPIO as input.""" + self.check_device_id(device_id) + gpio.devices[device_id].setup(port, gpio.IN) + self.check_port_free(device_id, port, gpio.IN) + + def write_output(self, device_id, port, value): + """Write a value to a GPIO.""" + self.check_port(device_id, port, gpio.OUT) + gpio.devices[device_id].write(port, value) + + def read_input(self, device_id, port): + """Read a value from a GPIO.""" + self.check_port(device_id, port, gpio.IN) + return gpio.devices[device_id].read(port) + + def read_adc_input(self, device_id, port): + """Read an ADC value from a GPIO ADC port.""" + self.check_port(device_id, port, gpio.IN) + self.check_device_id(device_id) + return gpio.devices[device_id].adc_read(port) + + def edge_detect(self, device_id, port, event_callback): + """Add detection for RISING and FALLING events.""" + self.check_port(device_id, port, gpio.IN) + gpio.devices[device_id].add_event_detect(port, event_callback, gpio.BOTH) + gpio.devices[device_id].notify = True diff --git a/homeassistant/components/numato/binary_sensor.py b/homeassistant/components/numato/binary_sensor.py new file mode 100644 index 00000000000..ff61cb3cbb0 --- /dev/null +++ b/homeassistant/components/numato/binary_sensor.py @@ -0,0 +1,120 @@ +"""Binary sensor platform integration for Numato USB GPIO expanders.""" +from functools import partial +import logging + +from numato_gpio import NumatoGpioError + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send + +from . import ( + CONF_BINARY_SENSORS, + CONF_DEVICES, + CONF_ID, + CONF_INVERT_LOGIC, + CONF_PORTS, + DATA_API, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +NUMATO_SIGNAL = "numato_signal_{}_{}" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the configured Numato USB GPIO binary sensor ports.""" + if discovery_info is None: + return + + def read_gpio(device_id, port, level): + """Send signal to entity to have it update state.""" + dispatcher_send(hass, NUMATO_SIGNAL.format(device_id, port), level) + + api = hass.data[DOMAIN][DATA_API] + binary_sensors = [] + devices = hass.data[DOMAIN][CONF_DEVICES] + for device in [d for d in devices if CONF_BINARY_SENSORS in d]: + device_id = device[CONF_ID] + platform = device[CONF_BINARY_SENSORS] + invert_logic = platform[CONF_INVERT_LOGIC] + ports = platform[CONF_PORTS] + for port, port_name in ports.items(): + try: + + api.setup_input(device_id, port) + api.edge_detect(device_id, port, partial(read_gpio, device_id)) + + except NumatoGpioError as err: + _LOGGER.error( + "Failed to initialize binary sensor '%s' on Numato device %s port %s: %s", + port_name, + device_id, + port, + err, + ) + continue + + binary_sensors.append( + NumatoGpioBinarySensor(port_name, device_id, port, invert_logic, api,) + ) + add_entities(binary_sensors, True) + + +class NumatoGpioBinarySensor(BinarySensorDevice): + """Represents a binary sensor (input) port of a Numato GPIO expander.""" + + def __init__(self, name, device_id, port, invert_logic, api): + """Initialize the Numato GPIO based binary sensor object.""" + self._name = name or DEVICE_DEFAULT_NAME + self._device_id = device_id + self._port = port + self._invert_logic = invert_logic + self._state = None + self._api = api + + async def async_added_to_hass(self): + """Connect state update callback.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + NUMATO_SIGNAL.format(self._device_id, self._port), + self._async_update_state, + ) + ) + + @callback + def _async_update_state(self, level): + """Update entity state.""" + self._state = level + self.async_write_ha_state() + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def is_on(self): + """Return the state of the entity.""" + return self._state != self._invert_logic + + def update(self): + """Update the GPIO state.""" + try: + self._state = self._api.read_input(self._device_id, self._port) + except NumatoGpioError as err: + self._state = None + _LOGGER.error( + "Failed to update Numato device %s port %s: %s", + self._device_id, + self._port, + err, + ) diff --git a/homeassistant/components/numato/manifest.json b/homeassistant/components/numato/manifest.json new file mode 100644 index 00000000000..4e9857cd579 --- /dev/null +++ b/homeassistant/components/numato/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "numato", + "name": "Numato USB GPIO Expander", + "documentation": "https://www.home-assistant.io/integrations/numato", + "requirements": ["numato-gpio==0.7.1"], + "codeowners": ["@clssn"], + "quality_scale": "internal" +} diff --git a/homeassistant/components/numato/sensor.py b/homeassistant/components/numato/sensor.py new file mode 100644 index 00000000000..e268d32a293 --- /dev/null +++ b/homeassistant/components/numato/sensor.py @@ -0,0 +1,123 @@ +"""Sensor platform integration for ADC ports of Numato USB GPIO expanders.""" +import logging + +from numato_gpio import NumatoGpioError + +from homeassistant.const import CONF_ID, CONF_NAME, CONF_SENSORS +from homeassistant.helpers.entity import Entity + +from . import ( + CONF_DEVICES, + CONF_DST_RANGE, + CONF_DST_UNIT, + CONF_PORTS, + CONF_SRC_RANGE, + DATA_API, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +ICON = "mdi:gauge" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the configured Numato USB GPIO ADC sensor ports.""" + if discovery_info is None: + return + + api = hass.data[DOMAIN][DATA_API] + sensors = [] + devices = hass.data[DOMAIN][CONF_DEVICES] + for device in [d for d in devices if CONF_SENSORS in d]: + device_id = device[CONF_ID] + ports = device[CONF_SENSORS][CONF_PORTS] + for port, adc_def in ports.items(): + try: + api.setup_input(device_id, port) + except NumatoGpioError as err: + _LOGGER.error( + "Failed to initialize sensor '%s' on Numato device %s port %s: %s", + adc_def[CONF_NAME], + device_id, + port, + err, + ) + continue + sensors.append( + NumatoGpioAdc( + adc_def[CONF_NAME], + device_id, + port, + adc_def[CONF_SRC_RANGE], + adc_def[CONF_DST_RANGE], + adc_def[CONF_DST_UNIT], + api, + ) + ) + add_entities(sensors, True) + + +class NumatoGpioAdc(Entity): + """Represents an ADC port of a Numato USB GPIO expander.""" + + def __init__(self, name, device_id, port, src_range, dst_range, dst_unit, api): + """Initialize the sensor.""" + self._name = name + self._device_id = device_id + self._port = port + self._src_range = src_range + self._dst_range = dst_range + self._state = None + self._unit_of_measurement = dst_unit + self._api = api + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """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_of_measurement + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + return ICON + + def update(self): + """Get the latest data and updates the state.""" + try: + adc_val = self._api.read_adc_input(self._device_id, self._port) + adc_val = self._clamp_to_source_range(adc_val) + self._state = self._linear_scale_to_dest_range(adc_val) + except NumatoGpioError as err: + self._state = None + _LOGGER.error( + "Failed to update Numato device %s ADC-port %s: %s", + self._device_id, + self._port, + err, + ) + + def _clamp_to_source_range(self, val): + # clamp to source range + val = max(val, self._src_range[0]) + val = min(val, self._src_range[1]) + return val + + def _linear_scale_to_dest_range(self, val): + # linear scale to dest range + src_len = self._src_range[1] - self._src_range[0] + adc_val_rel = val - self._src_range[0] + ratio = float(adc_val_rel) / float(src_len) + dst_len = self._dst_range[1] - self._dst_range[0] + dest_val = self._dst_range[0] + ratio * dst_len + return dest_val diff --git a/homeassistant/components/numato/switch.py b/homeassistant/components/numato/switch.py new file mode 100644 index 00000000000..2f1be0cf311 --- /dev/null +++ b/homeassistant/components/numato/switch.py @@ -0,0 +1,108 @@ +"""Switch platform integration for Numato USB GPIO expanders.""" +import logging + +from numato_gpio import NumatoGpioError + +from homeassistant.const import ( + CONF_DEVICES, + CONF_ID, + CONF_SWITCHES, + DEVICE_DEFAULT_NAME, +) +from homeassistant.helpers.entity import ToggleEntity + +from . import CONF_INVERT_LOGIC, CONF_PORTS, DATA_API, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the configured Numato USB GPIO switch ports.""" + if discovery_info is None: + return + + api = hass.data[DOMAIN][DATA_API] + switches = [] + devices = hass.data[DOMAIN][CONF_DEVICES] + for device in [d for d in devices if CONF_SWITCHES in d]: + device_id = device[CONF_ID] + platform = device[CONF_SWITCHES] + invert_logic = platform[CONF_INVERT_LOGIC] + ports = platform[CONF_PORTS] + for port, port_name in ports.items(): + try: + api.setup_output(device_id, port) + api.write_output(device_id, port, 1 if invert_logic else 0) + except NumatoGpioError as err: + _LOGGER.error( + "Failed to initialize switch '%s' on Numato device %s port %s: %s", + port_name, + device_id, + port, + err, + ) + continue + switches.append( + NumatoGpioSwitch(port_name, device_id, port, invert_logic, api,) + ) + add_entities(switches, True) + + +class NumatoGpioSwitch(ToggleEntity): + """Representation of a Numato USB GPIO switch port.""" + + def __init__(self, name, device_id, port, invert_logic, api): + """Initialize the port.""" + self._name = name or DEVICE_DEFAULT_NAME + self._device_id = device_id + self._port = port + self._invert_logic = invert_logic + self._state = False + self._api = api + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def is_on(self): + """Return true if port is turned on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the port on.""" + try: + self._api.write_output( + self._device_id, self._port, 0 if self._invert_logic else 1 + ) + self._state = True + self.schedule_update_ha_state() + except NumatoGpioError as err: + _LOGGER.error( + "Failed to turn on Numato device %s port %s: %s", + self._device_id, + self._port, + err, + ) + + def turn_off(self, **kwargs): + """Turn the port off.""" + try: + self._api.write_output( + self._device_id, self._port, 1 if self._invert_logic else 0 + ) + self._state = False + self.schedule_update_ha_state() + except NumatoGpioError as err: + _LOGGER.error( + "Failed to turn off Numato device %s port %s: %s", + self._device_id, + self._port, + err, + ) diff --git a/requirements_all.txt b/requirements_all.txt index 3a039d18832..74feb6866fc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -951,6 +951,9 @@ nsw-fuel-api-client==1.0.10 # homeassistant.components.nuheat nuheat==0.3.0 +# homeassistant.components.numato +numato-gpio==0.7.1 + # homeassistant.components.iqvia # homeassistant.components.opencv # homeassistant.components.tensorflow diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47362e55aee..07773687d88 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -377,6 +377,9 @@ nsw-fuel-api-client==1.0.10 # homeassistant.components.nuheat nuheat==0.3.0 +# homeassistant.components.numato +numato-gpio==0.7.1 + # homeassistant.components.iqvia # homeassistant.components.opencv # homeassistant.components.tensorflow diff --git a/tests/components/numato/__init__.py b/tests/components/numato/__init__.py new file mode 100644 index 00000000000..bcef00f9594 --- /dev/null +++ b/tests/components/numato/__init__.py @@ -0,0 +1 @@ +"""Tests for the numato integration.""" diff --git a/tests/components/numato/common.py b/tests/components/numato/common.py new file mode 100644 index 00000000000..18ece6690bc --- /dev/null +++ b/tests/components/numato/common.py @@ -0,0 +1,49 @@ +"""Definitions shared by all numato tests.""" + +from numato_gpio import NumatoGpioError + +NUMATO_CFG = { + "numato": { + "discover": ["/ttyACM0", "/ttyACM1"], + "devices": [ + { + "id": 0, + "binary_sensors": { + "invert_logic": False, + "ports": { + "2": "numato_binary_sensor_mock_port2", + "3": "numato_binary_sensor_mock_port3", + "4": "numato_binary_sensor_mock_port4", + }, + }, + "sensors": { + "ports": { + "1": { + "name": "numato_adc_mock_port1", + "source_range": [100, 1023], + "destination_range": [0, 10], + "unit": "mocks", + } + }, + }, + "switches": { + "invert_logic": False, + "ports": { + "5": "numato_switch_mock_port5", + "6": "numato_switch_mock_port6", + }, + }, + } + ], + } +} + + +def mockup_raise(*args, **kwargs): + """Mockup to replace regular functions for error injection.""" + raise NumatoGpioError("Error mockup") + + +def mockup_return(*args, **kwargs): + """Mockup to replace regular functions for error injection.""" + return False diff --git a/tests/components/numato/conftest.py b/tests/components/numato/conftest.py new file mode 100644 index 00000000000..c6fd13a099e --- /dev/null +++ b/tests/components/numato/conftest.py @@ -0,0 +1,28 @@ +"""Fixtures for numato tests.""" + +from copy import deepcopy + +import pytest + +from homeassistant.components import numato + +from . import numato_mock +from .common import NUMATO_CFG + + +@pytest.fixture +def config(): + """Provide a copy of the numato domain's test configuration. + + This helps to quickly change certain aspects of the configuration scoped + to each individual test. + """ + return deepcopy(NUMATO_CFG) + + +@pytest.fixture +def numato_fixture(monkeypatch): + """Inject the numato mockup into numato homeassistant module.""" + module_mock = numato_mock.NumatoModuleMock() + monkeypatch.setattr(numato, "gpio", module_mock) + return module_mock diff --git a/tests/components/numato/numato_mock.py b/tests/components/numato/numato_mock.py new file mode 100644 index 00000000000..1f8b24027de --- /dev/null +++ b/tests/components/numato/numato_mock.py @@ -0,0 +1,68 @@ +"""Mockup for the numato component interface.""" +from numato_gpio import NumatoGpioError + + +class NumatoModuleMock: + """Mockup for the numato_gpio module.""" + + NumatoGpioError = NumatoGpioError + + def __init__(self): + """Initialize the numato_gpio module mockup class.""" + self.devices = {} + + class NumatoDeviceMock: + """Mockup for the numato_gpio.NumatoUsbGpio class.""" + + def __init__(self, device): + """Initialize numato device mockup.""" + self.device = device + self.callbacks = {} + self.ports = set() + self.values = {} + + def setup(self, port, direction): + """Mockup for setup.""" + self.ports.add(port) + self.values[port] = None + + def write(self, port, value): + """Mockup for write.""" + self.values[port] = value + + def read(self, port): + """Mockup for read.""" + return 1 + + def adc_read(self, port): + """Mockup for adc_read.""" + return 1023 + + def add_event_detect(self, port, callback, direction): + """Mockup for add_event_detect.""" + self.callbacks[port] = callback + + def notify(self, enable): + """Mockup for notify.""" + + def mockup_inject_notification(self, port, value): + """Make the mockup execute a notification callback.""" + self.callbacks[port](port, value) + + OUT = 0 + IN = 1 + + RISING = 1 + FALLING = 2 + BOTH = 3 + + def discover(self, _=None): + """Mockup for the numato device discovery. + + Ignore the device list argument, mock discovers /dev/ttyACM0. + """ + self.devices[0] = NumatoModuleMock.NumatoDeviceMock("/dev/ttyACM0") + + def cleanup(self): + """Mockup for the numato device cleanup.""" + self.devices.clear() diff --git a/tests/components/numato/test_binary_sensor.py b/tests/components/numato/test_binary_sensor.py new file mode 100644 index 00000000000..5aa6aea2b8d --- /dev/null +++ b/tests/components/numato/test_binary_sensor.py @@ -0,0 +1,62 @@ +"""Tests for the numato binary_sensor platform.""" +from homeassistant.helpers import discovery +from homeassistant.setup import async_setup_component + +from .common import NUMATO_CFG, mockup_raise + +MOCKUP_ENTITY_IDS = { + "binary_sensor.numato_binary_sensor_mock_port2", + "binary_sensor.numato_binary_sensor_mock_port3", + "binary_sensor.numato_binary_sensor_mock_port4", +} + + +async def test_failing_setups_no_entities(hass, numato_fixture, monkeypatch): + """When port setup fails, no entity shall be created.""" + monkeypatch.setattr(numato_fixture.NumatoDeviceMock, "setup", mockup_raise) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + + +async def test_setup_callbacks(hass, numato_fixture, monkeypatch): + """During setup a callback shall be registered.""" + + numato_fixture.discover() + + def mock_add_event_detect(self, port, callback, direction): + assert self == numato_fixture.devices[0] + assert port == 1 + assert callback is callable + assert direction == numato_fixture.BOTH + + monkeypatch.setattr( + numato_fixture.devices[0], "add_event_detect", mock_add_event_detect + ) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + + +async def test_hass_binary_sensor_notification(hass, numato_fixture): + """Test regular operations from within Home Assistant.""" + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() # wait until services are registered + assert ( + hass.states.get("binary_sensor.numato_binary_sensor_mock_port2").state == "on" + ) + await hass.async_add_executor_job(numato_fixture.devices[0].callbacks[2], 2, False) + await hass.async_block_till_done() + assert ( + hass.states.get("binary_sensor.numato_binary_sensor_mock_port2").state == "off" + ) + + +async def test_binary_sensor_setup_without_discovery_info(hass, config, numato_fixture): + """Test handling of empty discovery_info.""" + numato_fixture.discover() + await discovery.async_load_platform(hass, "binary_sensor", "numato", None, config) + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + await hass.async_block_till_done() # wait for numato platform to be loaded + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id in hass.states.async_entity_ids() diff --git a/tests/components/numato/test_init.py b/tests/components/numato/test_init.py new file mode 100644 index 00000000000..dd5643be12a --- /dev/null +++ b/tests/components/numato/test_init.py @@ -0,0 +1,161 @@ +"""Tests for the numato integration.""" +from numato_gpio import NumatoGpioError +import pytest + +from homeassistant.components import numato +from homeassistant.setup import async_setup_component + +from .common import NUMATO_CFG, mockup_raise, mockup_return + + +async def test_setup_no_devices(hass, numato_fixture, monkeypatch): + """Test handling of an 'empty' discovery. + + Platform setups are expected to return after handling errors locally + without raising. + """ + monkeypatch.setattr(numato_fixture, "discover", mockup_return) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + assert len(numato_fixture.devices) == 0 + + +async def test_fail_setup_raising_discovery(hass, numato_fixture, caplog, monkeypatch): + """Test handling of an exception during discovery. + + Setup shall return False. + """ + monkeypatch.setattr(numato_fixture, "discover", mockup_raise) + assert not await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() + + +async def test_hass_numato_api_wrong_port_directions(hass, numato_fixture): + """Test handling of wrong port directions. + + This won't happen in the current platform implementation but would raise + in case of an introduced bug in the platforms. + """ + numato_fixture.discover() + api = numato.NumatoAPI() + api.setup_output(0, 5) + api.setup_input(0, 2) + api.setup_input(0, 6) + with pytest.raises(NumatoGpioError): + api.read_adc_input(0, 5) # adc_read from output + api.read_input(0, 6) # read from output + api.write_output(0, 2, 1) # write to input + + +async def test_hass_numato_api_errors(hass, numato_fixture, monkeypatch): + """Test whether Home Assistant numato API (re-)raises errors.""" + numato_fixture.discover() + monkeypatch.setattr(numato_fixture.devices[0], "setup", mockup_raise) + monkeypatch.setattr(numato_fixture.devices[0], "adc_read", mockup_raise) + monkeypatch.setattr(numato_fixture.devices[0], "read", mockup_raise) + monkeypatch.setattr(numato_fixture.devices[0], "write", mockup_raise) + api = numato.NumatoAPI() + with pytest.raises(NumatoGpioError): + api.setup_input(0, 5) + api.read_adc_input(0, 1) + api.read_input(0, 2) + api.write_output(0, 2, 1) + + +async def test_invalid_port_number(hass, numato_fixture, config): + """Test validation of ADC port number type.""" + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + port1_config = sensorports_cfg["1"] + sensorports_cfg["one"] = port1_config + del sensorports_cfg["1"] + assert not await async_setup_component(hass, "numato", config) + await hass.async_block_till_done() + assert not numato_fixture.devices + + +async def test_too_low_adc_port_number(hass, numato_fixture, config): + """Test handling of failing component setup. + + Tries setting up an ADC on a port below (0) the allowed range. + """ + + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg.update({0: {"name": "toolow"}}) + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_too_high_adc_port_number(hass, numato_fixture, config): + """Test handling of failing component setup. + + Tries setting up an ADC on a port above (8) the allowed range. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg.update({8: {"name": "toohigh"}}) + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_range_value_type(hass, numato_fixture, config): + """Test validation of ADC range config's types. + + Replaces the source range beginning by a string. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["source_range"][0] = "zero" + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_source_range_length(hass, numato_fixture, config): + """Test validation of ADC range config's length. + + Adds an element to the source range. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["source_range"].append(42) + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_source_range_order(hass, numato_fixture, config): + """Test validation of ADC range config's order. + + Sets the source range to a decreasing [2, 1]. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["source_range"] = [2, 1] + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_destination_range_value_type(hass, numato_fixture, config): + """Test validation of ADC range . + + Replaces the destination range beginning by a string. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["destination_range"][0] = "zero" + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_destination_range_length(hass, numato_fixture, config): + """Test validation of ADC range config's length. + + Adds an element to the destination range. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["destination_range"].append(42) + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices + + +async def test_invalid_adc_destination_range_order(hass, numato_fixture, config): + """Test validation of ADC range config's order. + + Sets the destination range to a decreasing [2, 1]. + """ + sensorports_cfg = config["numato"]["devices"][0]["sensors"]["ports"] + sensorports_cfg["1"]["destination_range"] = [2, 1] + assert not await async_setup_component(hass, "numato", config) + assert not numato_fixture.devices diff --git a/tests/components/numato/test_sensor.py b/tests/components/numato/test_sensor.py new file mode 100644 index 00000000000..c6d176dbc90 --- /dev/null +++ b/tests/components/numato/test_sensor.py @@ -0,0 +1,38 @@ +"""Tests for the numato sensor platform.""" +from homeassistant.const import STATE_UNKNOWN +from homeassistant.helpers import discovery +from homeassistant.setup import async_setup_component + +from .common import NUMATO_CFG, mockup_raise + +MOCKUP_ENTITY_IDS = { + "sensor.numato_adc_mock_port1", +} + + +async def test_failing_setups_no_entities(hass, numato_fixture, monkeypatch): + """When port setup fails, no entity shall be created.""" + monkeypatch.setattr(numato_fixture.NumatoDeviceMock, "setup", mockup_raise) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + + +async def test_failing_sensor_update(hass, numato_fixture, monkeypatch): + """Test condition when a sensor update fails.""" + monkeypatch.setattr(numato_fixture.NumatoDeviceMock, "adc_read", mockup_raise) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() + assert hass.states.get("sensor.numato_adc_mock_port1").state is STATE_UNKNOWN + + +async def test_sensor_setup_without_discovery_info(hass, config, numato_fixture): + """Test handling of empty discovery_info.""" + numato_fixture.discover() + await discovery.async_load_platform(hass, "sensor", "numato", None, config) + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + await hass.async_block_till_done() # wait for numato platform to be loaded + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id in hass.states.async_entity_ids() diff --git a/tests/components/numato/test_switch.py b/tests/components/numato/test_switch.py new file mode 100644 index 00000000000..91cda5c2a37 --- /dev/null +++ b/tests/components/numato/test_switch.py @@ -0,0 +1,114 @@ +"""Tests for the numato switch platform.""" +from homeassistant.components import switch +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.helpers import discovery +from homeassistant.setup import async_setup_component + +from .common import NUMATO_CFG, mockup_raise + +MOCKUP_ENTITY_IDS = { + "switch.numato_switch_mock_port5", + "switch.numato_switch_mock_port6", +} + + +async def test_failing_setups_no_entities(hass, numato_fixture, monkeypatch): + """When port setup fails, no entity shall be created.""" + monkeypatch.setattr(numato_fixture.NumatoDeviceMock, "setup", mockup_raise) + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + + +async def test_regular_hass_operations(hass, numato_fixture): + """Test regular operations from within Home Assistant.""" + assert await async_setup_component(hass, "numato", NUMATO_CFG) + await hass.async_block_till_done() # wait until services are registered + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port5"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port5").state == "on" + assert numato_fixture.devices[0].values[5] == 1 + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port6"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port6").state == "on" + assert numato_fixture.devices[0].values[6] == 1 + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port5"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port5").state == "off" + assert numato_fixture.devices[0].values[5] == 0 + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port6"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port6").state == "off" + assert numato_fixture.devices[0].values[6] == 0 + + +async def test_failing_hass_operations(hass, numato_fixture, monkeypatch): + """Test failing operations called from within Home Assistant. + + Switches remain in their initial 'off' state when the device can't + be written to. + """ + assert await async_setup_component(hass, "numato", NUMATO_CFG) + + await hass.async_block_till_done() # wait until services are registered + monkeypatch.setattr(numato_fixture.devices[0], "write", mockup_raise) + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port5"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port5").state == "off" + assert not numato_fixture.devices[0].values[5] + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port6"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port6").state == "off" + assert not numato_fixture.devices[0].values[6] + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port5"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port5").state == "off" + assert not numato_fixture.devices[0].values[5] + await hass.services.async_call( + switch.DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "switch.numato_switch_mock_port6"}, + blocking=True, + ) + assert hass.states.get("switch.numato_switch_mock_port6").state == "off" + assert not numato_fixture.devices[0].values[6] + + +async def test_switch_setup_without_discovery_info(hass, config, numato_fixture): + """Test handling of empty discovery_info.""" + numato_fixture.discover() + await discovery.async_load_platform(hass, "switch", "numato", None, config) + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id not in hass.states.async_entity_ids() + await hass.async_block_till_done() # wait for numato platform to be loaded + for entity_id in MOCKUP_ENTITY_IDS: + assert entity_id in hass.states.async_entity_ids() From a65edc8dc146a8424bfd2a11f59090538cdd265a Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Thu, 30 Apr 2020 14:52:53 +0200 Subject: [PATCH 153/511] Fix crash in NAD integration (#34571) Some amplifiers/receivers do not report any volume (due to them not knowing), for example NAD C 356BEE is documented to return an empty string for volume. At least don't crash when we get None back. --- homeassistant/components/nad/media_player.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index c9015b85fd1..2d9afbb7541 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -197,7 +197,10 @@ class NAD(MediaPlayerEntity): else: self._mute = True - self._volume = self.calc_volume(self._nad_receiver.main_volume("?")) + volume = self._nad_receiver.main_volume("?") + # Some receivers cannot report the volume, e.g. C 356BEE, + # instead they only support stepping the volume up or down + self._volume = self.calc_volume(volume) if volume is not None else None self._source = self._source_dict.get(self._nad_receiver.main_source("?")) def calc_volume(self, decibel): From 29a05a6a65be590ab83bccc702b95dd56fb65c51 Mon Sep 17 00:00:00 2001 From: Thomas Le Gentil <20202649+kifeo@users.noreply.github.com> Date: Thu, 30 Apr 2020 15:08:11 +0200 Subject: [PATCH 154/511] Add fortigate deprecation message (#34854) --- .../components/fortigate/__init__.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/fortigate/__init__.py b/homeassistant/components/fortigate/__init__.py index 6de55ae3d65..2dbd7ef45c0 100644 --- a/homeassistant/components/fortigate/__init__.py +++ b/homeassistant/components/fortigate/__init__.py @@ -21,18 +21,21 @@ DOMAIN = "fortigate" DATA_FGT = DOMAIN CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_DEVICES, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), - } - ) - }, + vol.All( + cv.deprecated(DOMAIN, invalidation_version="0.112.0"), + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_DEVICES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) From 55bf5514ad6a6cf5945a398001671ac46169ac06 Mon Sep 17 00:00:00 2001 From: alxrdn Date: Thu, 30 Apr 2020 15:30:37 +0200 Subject: [PATCH 155/511] Add overlay options wrapper to rpi_camera (#34461) * add overlay options wrapper to rpi_camera * Refactor to set config yaml section under the top level integration domain key * Remove return values that are not checked Co-Authored-By: Martin Hjelmare * Remove superfluous debug log messages * Return if not set up via discovery * Add convenience reference to hass.data[DOMAIN] * Black formatting * Isort * Exclude all rpi_camera modules Co-authored-by: Martin Hjelmare --- .coveragerc | 2 +- .../components/rpi_camera/__init__.py | 88 +++++++++++++++++ homeassistant/components/rpi_camera/camera.py | 94 ++++++++----------- homeassistant/components/rpi_camera/const.py | 22 +++++ 4 files changed, 148 insertions(+), 58 deletions(-) create mode 100644 homeassistant/components/rpi_camera/const.py diff --git a/.coveragerc b/.coveragerc index cf9fa59397d..e2aa4243a46 100644 --- a/.coveragerc +++ b/.coveragerc @@ -611,7 +611,7 @@ omit = homeassistant/components/roomba/vacuum.py homeassistant/components/route53/* homeassistant/components/rova/sensor.py - homeassistant/components/rpi_camera/camera.py + homeassistant/components/rpi_camera/* homeassistant/components/rpi_gpio/* homeassistant/components/rpi_gpio/cover.py homeassistant/components/rpi_gpio_pwm/light.py diff --git a/homeassistant/components/rpi_camera/__init__.py b/homeassistant/components/rpi_camera/__init__.py index 04638e463a1..2f962872d8c 100644 --- a/homeassistant/components/rpi_camera/__init__.py +++ b/homeassistant/components/rpi_camera/__init__.py @@ -1 +1,89 @@ """The rpi_camera component.""" +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_FILE_PATH, CONF_NAME +from homeassistant.helpers import config_validation as cv, discovery + +from .const import ( + CONF_HORIZONTAL_FLIP, + CONF_IMAGE_HEIGHT, + CONF_IMAGE_QUALITY, + CONF_IMAGE_ROTATION, + CONF_IMAGE_WIDTH, + CONF_OVERLAY_METADATA, + CONF_OVERLAY_TIMESTAMP, + CONF_TIMELAPSE, + CONF_VERTICAL_FLIP, + DEFAULT_HORIZONTAL_FLIP, + DEFAULT_IMAGE_HEIGHT, + DEFAULT_IMAGE_QUALITY, + DEFAULT_IMAGE_ROTATION, + DEFAULT_IMAGE_WIDTH, + DEFAULT_NAME, + DEFAULT_TIMELAPSE, + DEFAULT_VERTICAL_FLIP, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_FILE_PATH): cv.isfile, + vol.Optional( + CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), + vol.Optional( + CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT + ): vol.Coerce(int), + vol.Optional( + CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), + vol.Optional( + CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=359)), + vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): vol.Coerce( + int + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OVERLAY_METADATA): vol.All( + vol.Coerce(int), vol.Range(min=4, max=2056) + ), + vol.Optional(CONF_OVERLAY_TIMESTAMP): cv.string, + vol.Optional(CONF_TIMELAPSE, default=DEFAULT_TIMELAPSE): vol.Coerce( + int + ), + vol.Optional( + CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP + ): vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +def setup(hass, config): + """Set up the rpi_camera integration.""" + config_domain = config[DOMAIN] + hass.data[DOMAIN] = { + CONF_FILE_PATH: config_domain.get(CONF_FILE_PATH), + CONF_HORIZONTAL_FLIP: config_domain.get(CONF_HORIZONTAL_FLIP), + CONF_IMAGE_WIDTH: config_domain.get(CONF_IMAGE_WIDTH), + CONF_IMAGE_HEIGHT: config_domain.get(CONF_IMAGE_HEIGHT), + CONF_IMAGE_QUALITY: config_domain.get(CONF_IMAGE_QUALITY), + CONF_IMAGE_ROTATION: config_domain.get(CONF_IMAGE_ROTATION), + CONF_NAME: config_domain.get(CONF_NAME), + CONF_OVERLAY_METADATA: config_domain.get(CONF_OVERLAY_METADATA), + CONF_OVERLAY_TIMESTAMP: config_domain.get(CONF_OVERLAY_TIMESTAMP), + CONF_TIMELAPSE: config_domain.get(CONF_TIMELAPSE), + CONF_VERTICAL_FLIP: config_domain.get(CONF_VERTICAL_FLIP), + } + + discovery.load_platform(hass, "camera", DOMAIN, {}, config) + + return True diff --git a/homeassistant/components/rpi_camera/camera.py b/homeassistant/components/rpi_camera/camera.py index bf04e0ef492..47ce87c4a8d 100644 --- a/homeassistant/components/rpi_camera/camera.py +++ b/homeassistant/components/rpi_camera/camera.py @@ -5,53 +5,24 @@ import shutil import subprocess from tempfile import NamedTemporaryFile -import voluptuous as vol - -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera +from homeassistant.components.camera import Camera from homeassistant.const import CONF_FILE_PATH, CONF_NAME, EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers import config_validation as cv + +from .const import ( + CONF_HORIZONTAL_FLIP, + CONF_IMAGE_HEIGHT, + CONF_IMAGE_QUALITY, + CONF_IMAGE_ROTATION, + CONF_IMAGE_WIDTH, + CONF_OVERLAY_METADATA, + CONF_OVERLAY_TIMESTAMP, + CONF_TIMELAPSE, + CONF_VERTICAL_FLIP, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) -CONF_HORIZONTAL_FLIP = "horizontal_flip" -CONF_IMAGE_HEIGHT = "image_height" -CONF_IMAGE_QUALITY = "image_quality" -CONF_IMAGE_ROTATION = "image_rotation" -CONF_IMAGE_WIDTH = "image_width" -CONF_TIMELAPSE = "timelapse" -CONF_VERTICAL_FLIP = "vertical_flip" - -DEFAULT_HORIZONTAL_FLIP = 0 -DEFAULT_IMAGE_HEIGHT = 480 -DEFAULT_IMAGE_QUALITY = 7 -DEFAULT_IMAGE_ROTATION = 0 -DEFAULT_IMAGE_WIDTH = 640 -DEFAULT_NAME = "Raspberry Pi Camera" -DEFAULT_TIMELAPSE = 1000 -DEFAULT_VERTICAL_FLIP = 0 - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_FILE_PATH): cv.isfile, - vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP): vol.All( - vol.Coerce(int), vol.Range(min=0, max=1) - ), - vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT): vol.Coerce(int), - vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY): vol.All( - vol.Coerce(int), vol.Range(min=0, max=100) - ), - vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION): vol.All( - vol.Coerce(int), vol.Range(min=0, max=359) - ), - vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): vol.Coerce(int), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TIMELAPSE, default=1000): vol.Coerce(int), - vol.Optional(CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP): vol.All( - vol.Coerce(int), vol.Range(min=0, max=1) - ), - } -) - def kill_raspistill(*args): """Kill any previously running raspistill process..""" @@ -62,24 +33,18 @@ def kill_raspistill(*args): def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Raspberry Camera.""" + # We only want this platform to be set up via discovery. + # prevent initializing by erroneous platform config section in yaml conf + if discovery_info is None: + return + if shutil.which("raspistill") is None: _LOGGER.error("'raspistill' was not found") - return False - - setup_config = { - CONF_NAME: config.get(CONF_NAME), - CONF_IMAGE_WIDTH: config.get(CONF_IMAGE_WIDTH), - CONF_IMAGE_HEIGHT: config.get(CONF_IMAGE_HEIGHT), - CONF_IMAGE_QUALITY: config.get(CONF_IMAGE_QUALITY), - CONF_IMAGE_ROTATION: config.get(CONF_IMAGE_ROTATION), - CONF_TIMELAPSE: config.get(CONF_TIMELAPSE), - CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP), - CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP), - CONF_FILE_PATH: config.get(CONF_FILE_PATH), - } + return hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill) + setup_config = hass.data[DOMAIN] file_path = setup_config[CONF_FILE_PATH] def delete_temp_file(*args): @@ -100,7 +65,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # Check whether the file path has been whitelisted elif not hass.config.is_allowed_path(file_path): _LOGGER.error("'%s' is not a whitelisted directory", file_path) - return False + return add_entities([RaspberryCamera(setup_config)]) @@ -142,6 +107,16 @@ class RaspberryCamera(Camera): if device_info[CONF_VERTICAL_FLIP]: cmd_args.append("-vf") + if device_info[CONF_OVERLAY_METADATA]: + cmd_args.append("-a") + cmd_args.append(str(device_info[CONF_OVERLAY_METADATA])) + + if device_info[CONF_OVERLAY_TIMESTAMP]: + cmd_args.append("-a") + cmd_args.append("4") + cmd_args.append("-a") + cmd_args.append(str(device_info[CONF_OVERLAY_TIMESTAMP])) + subprocess.Popen(cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) def camera_image(self): @@ -153,3 +128,8 @@ class RaspberryCamera(Camera): def name(self): """Return the name of this camera.""" return self._name + + @property + def frame_interval(self): + """Return the interval between frames of the stream.""" + return self._config[CONF_TIMELAPSE] / 1000 diff --git a/homeassistant/components/rpi_camera/const.py b/homeassistant/components/rpi_camera/const.py new file mode 100644 index 00000000000..19da9e2a286 --- /dev/null +++ b/homeassistant/components/rpi_camera/const.py @@ -0,0 +1,22 @@ +"""Consts used by rpi_camera.""" + +DOMAIN = "rpi_camera" + +CONF_HORIZONTAL_FLIP = "horizontal_flip" +CONF_IMAGE_HEIGHT = "image_height" +CONF_IMAGE_QUALITY = "image_quality" +CONF_IMAGE_ROTATION = "image_rotation" +CONF_IMAGE_WIDTH = "image_width" +CONF_OVERLAY_METADATA = "overlay_metadata" +CONF_OVERLAY_TIMESTAMP = "overlay_timestamp" +CONF_TIMELAPSE = "timelapse" +CONF_VERTICAL_FLIP = "vertical_flip" + +DEFAULT_HORIZONTAL_FLIP = 0 +DEFAULT_IMAGE_HEIGHT = 480 +DEFAULT_IMAGE_QUALITY = 7 +DEFAULT_IMAGE_ROTATION = 0 +DEFAULT_IMAGE_WIDTH = 640 +DEFAULT_NAME = "Raspberry Pi Camera" +DEFAULT_TIMELAPSE = 1000 +DEFAULT_VERTICAL_FLIP = 0 From d43617c41d6507f2d2b77aadf4fa1ebaf0058b14 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 30 Apr 2020 15:43:02 +0200 Subject: [PATCH 156/511] Bump brother to 0.1.14 (#34930) --- 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 48df788c93a..7f59aaa9c2c 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.1.13"], + "requirements": ["brother==0.1.14"], "zeroconf": ["_printer._tcp.local."], "config_flow": true, "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 74feb6866fc..d166d68ae7f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -365,7 +365,7 @@ bravia-tv==1.0.2 broadlink==0.13.2 # homeassistant.components.brother -brother==0.1.13 +brother==0.1.14 # homeassistant.components.brottsplatskartan brottsplatskartan==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 07773687d88..607acf46478 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -150,7 +150,7 @@ bravia-tv==1.0.2 broadlink==0.13.2 # homeassistant.components.brother -brother==0.1.13 +brother==0.1.14 # homeassistant.components.buienradar buienradar==1.0.4 From bf5cc22bef3a65e278056dd94b51c78e5089272c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 13:34:25 -0500 Subject: [PATCH 157/511] Fix preservation of homekit fan speed on toggle (#34971) --- homeassistant/components/homekit/type_fans.py | 4 ++- homeassistant/components/homekit/util.py | 2 +- tests/components/homekit/test_type_fans.py | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index b7208b1746c..291b3ffed90 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -165,7 +165,9 @@ class Fan(HomeAccessory): self.char_direction.set_value(hk_direction) # Handle Speed - if self.char_speed is not None: + if self.char_speed is not None and state != STATE_OFF: + # We do not change the homekit speed when turning off + # as it will clear the restore state speed = new_state.attributes.get(ATTR_SPEED) hk_speed_value = self.speed_mapping.speed_to_homekit(speed) if hk_speed_value is not None and self.char_speed.value != hk_speed_value: diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 0295440df49..b8d98ad2304 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -200,7 +200,7 @@ class HomeKitSpeedMapping: if speed is None: return None speed_range = self.speed_ranges[speed] - return speed_range.target + return round(speed_range.target) def speed_to_states(self, speed): """Map HomeKit speed to Home Assistant speed state.""" diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index ca6e03217f3..4d2ace24eab 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -304,6 +304,7 @@ async def test_fan_speed(hass, hk_driver, cls, events): call_set_speed = async_mock_service(hass, DOMAIN, "set_speed") char_speed_iid = acc.char_speed.to_HAP()[HAP_REPR_IID] + char_active_iid = acc.char_active.to_HAP()[HAP_REPR_IID] hk_driver.set_characteristics( { @@ -320,12 +321,37 @@ async def test_fan_speed(hass, hk_driver, cls, events): await hass.async_add_executor_job(acc.char_speed.client_update_value, 42) await hass.async_block_till_done() acc.speed_mapping.speed_to_states.assert_called_with(42) + assert acc.char_speed.value == 42 + assert acc.char_active.value == 1 + assert call_set_speed[0] assert call_set_speed[0].data[ATTR_ENTITY_ID] == entity_id assert call_set_speed[0].data[ATTR_SPEED] == "ludicrous" assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "ludicrous" + # Verify speed is preserved from off to on + hass.states.async_set(entity_id, STATE_OFF, {ATTR_SPEED: SPEED_OFF}) + await hass.async_block_till_done() + assert acc.char_speed.value == 42 + assert acc.char_active.value == 0 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_active_iid, + HAP_REPR_VALUE: 1, + }, + ] + }, + "mock_addr", + ) + await hass.async_block_till_done() + assert acc.char_speed.value == 42 + assert acc.char_active.value == 1 + async def test_fan_set_all_one_shot(hass, hk_driver, cls, events): """Test fan with speed.""" From 435a88636a2a13c777ed7e831b198ade3c7a77f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 30 Apr 2020 22:37:58 +0300 Subject: [PATCH 158/511] Address new issues flagged by flake8 3.8.0a2 (#34964) --- homeassistant/components/discord/notify.py | 1 + homeassistant/components/group/light.py | 2 +- .../components/streamlabswater/__init__.py | 4 +++- homeassistant/components/wunderlist/__init__.py | 2 +- homeassistant/components/zha/core/helpers.py | 2 +- script/scaffold/docs.py | 2 +- .../geo_json_events/test_geo_location.py | 4 ++-- tests/components/huawei_lte/test_config_flow.py | 2 +- .../minecraft_server/test_config_flow.py | 4 ++-- tests/components/ssdp/test_init.py | 4 ++-- tests/components/system_log/test_init.py | 2 +- .../test/alarm_control_panel.py | 4 ++-- .../custom_components/test/cover.py | 16 ++++++++-------- .../custom_components/test/lock.py | 4 ++-- 14 files changed, 28 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/discord/notify.py b/homeassistant/components/discord/notify.py index fb36a60eecc..11f83d80179 100644 --- a/homeassistant/components/discord/notify.py +++ b/homeassistant/components/discord/notify.py @@ -65,6 +65,7 @@ class DiscordNotificationService(BaseNotificationService): images.append(image) else: _LOGGER.warning("Image not found: %s", image) + # pylint: disable=unused-variable @discord_bot.event async def on_ready(): diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index eb043177eba..56408f410b8 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -326,7 +326,7 @@ def _mean_int(*args): def _mean_tuple(*args): """Return the mean values along the columns of the supplied values.""" - return tuple(sum(l) / len(l) for l in zip(*args)) + return tuple(sum(x) / len(x) for x in zip(*args)) def _reduce_attribute( diff --git a/homeassistant/components/streamlabswater/__init__.py b/homeassistant/components/streamlabswater/__init__.py index 836bc9b4183..336e92358ee 100644 --- a/homeassistant/components/streamlabswater/__init__.py +++ b/homeassistant/components/streamlabswater/__init__.py @@ -59,7 +59,9 @@ def setup(hass, config): "Streamlabs Water Monitor auto-detected location_id=%s", location_id ) else: - location = next((l for l in locations if location_id == l["locationId"]), None) + location = next( + (loc for loc in locations if location_id == loc["locationId"]), None + ) if location is None: _LOGGER.error("Supplied location_id is invalid") return False diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py index 4d9ff6e2235..954088e4b21 100644 --- a/homeassistant/components/wunderlist/__init__.py +++ b/homeassistant/components/wunderlist/__init__.py @@ -85,7 +85,7 @@ class Wunderlist: def _list_by_name(self, name): """Return a list ID by name.""" lists = self._client.get_lists() - tmp = [l for l in lists if l["title"] == name] + tmp = [lst for lst in lists if lst["title"] == name] if tmp: return tmp[0]["id"] return None diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 4441ac90717..bb8a202e789 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -101,7 +101,7 @@ def mean_int(*args): def mean_tuple(*args): """Return the mean values along the columns of the supplied values.""" - return tuple(sum(l) / len(l) for l in zip(*args)) + return tuple(sum(x) / len(x) for x in zip(*args)) def reduce_attribute( diff --git a/script/scaffold/docs.py b/script/scaffold/docs.py index 8186b857e80..f4416a7b7e8 100644 --- a/script/scaffold/docs.py +++ b/script/scaffold/docs.py @@ -69,7 +69,7 @@ def print_relevant_docs(template: str, info: Info) -> None: print() print( - f"The next step is to look at the files and deal with all areas marked as TODO." + "The next step is to look at the files and deal with all areas marked as TODO." ) if "extra" in data: diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index e10125f84ac..6b7535a8c85 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -189,8 +189,8 @@ async def test_setup_race_condition(hass): # Set up some mock feed entries for this test. mock_entry_1 = _generate_mock_feed_entry("1234", "Title 1", 15.5, (-31.0, 150.0)) - delete_signal = f"geo_json_events_delete_1234" - update_signal = f"geo_json_events_update_1234" + delete_signal = "geo_json_events_delete_1234" + update_signal = "geo_json_events_update_1234" # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() diff --git a/tests/components/huawei_lte/test_config_flow.py b/tests/components/huawei_lte/test_config_flow.py index 86de1ad8bd1..ae1e8184727 100644 --- a/tests/components/huawei_lte/test_config_flow.py +++ b/tests/components/huawei_lte/test_config_flow.py @@ -138,7 +138,7 @@ async def test_success(hass, login_requests_mock): login_requests_mock.request( ANY, f"{FIXTURE_USER_INPUT[CONF_URL]}api/user/login", - text=f"OK", + text="OK", ) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER}, data=FIXTURE_USER_INPUT diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index bc49ed08109..7db6dc33b5a 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -68,12 +68,12 @@ USER_INPUT_IPV6 = { USER_INPUT_PORT_TOO_SMALL = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: f"mc.dummyserver.com:1023", + CONF_HOST: "mc.dummyserver.com:1023", } USER_INPUT_PORT_TOO_LARGE = { CONF_NAME: DEFAULT_NAME, - CONF_HOST: f"mc.dummyserver.com:65536", + CONF_HOST: "mc.dummyserver.com:65536", } SRV_RECORDS = asyncio.Future() diff --git a/tests/components/ssdp/test_init.py b/tests/components/ssdp/test_init.py index 62b7c2bbde2..b6499af5601 100644 --- a/tests/components/ssdp/test_init.py +++ b/tests/components/ssdp/test_init.py @@ -61,7 +61,7 @@ async def test_scan_not_all_present(hass, aioclient_mock): """Test match fails if some specified attributes are not present.""" aioclient_mock.get( "http://1.1.1.1", - text=f""" + text=""" Paulus @@ -96,7 +96,7 @@ async def test_scan_not_all_match(hass, aioclient_mock): """Test match fails if some specified attribute values differ.""" aioclient_mock.get( "http://1.1.1.1", - text=f""" + text=""" Paulus diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 92f0ed9fd16..6408b1625f5 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -139,7 +139,7 @@ async def test_remove_older_logs(hass, hass_client): def log_msg(nr=2): """Log an error at same line.""" - _LOGGER.error(f"error message %s", nr) + _LOGGER.error("error message %s", nr) async def test_dedup_logs(hass, hass_client): diff --git a/tests/testing_config/custom_components/test/alarm_control_panel.py b/tests/testing_config/custom_components/test/alarm_control_panel.py index 5edb1ca2f70..6535f5aa1f5 100644 --- a/tests/testing_config/custom_components/test/alarm_control_panel.py +++ b/tests/testing_config/custom_components/test/alarm_control_panel.py @@ -32,12 +32,12 @@ def init(empty=False): if empty else { "arm_code": MockAlarm( - name=f"Alarm arm code", + name="Alarm arm code", code_arm_required=True, unique_id="unique_arm_code", ), "no_arm_code": MockAlarm( - name=f"Alarm no arm code", + name="Alarm no arm code", code_arm_required=False, unique_id="unique_no_arm_code", ), diff --git a/tests/testing_config/custom_components/test/cover.py b/tests/testing_config/custom_components/test/cover.py index f7b0dae15ee..095489ce7b4 100644 --- a/tests/testing_config/custom_components/test/cover.py +++ b/tests/testing_config/custom_components/test/cover.py @@ -29,29 +29,29 @@ def init(empty=False): if empty else [ MockCover( - name=f"Simple cover", + name="Simple cover", is_on=True, - unique_id=f"unique_cover", + unique_id="unique_cover", supports_tilt=False, ), MockCover( - name=f"Set position cover", + name="Set position cover", is_on=True, - unique_id=f"unique_set_pos_cover", + unique_id="unique_set_pos_cover", current_cover_position=50, supports_tilt=False, ), MockCover( - name=f"Set tilt position cover", + name="Set tilt position cover", is_on=True, - unique_id=f"unique_set_pos_tilt_cover", + unique_id="unique_set_pos_tilt_cover", current_cover_tilt_position=50, supports_tilt=True, ), MockCover( - name=f"Tilt cover", + name="Tilt cover", is_on=True, - unique_id=f"unique_tilt_cover", + unique_id="unique_tilt_cover", supports_tilt=True, ), ] diff --git a/tests/testing_config/custom_components/test/lock.py b/tests/testing_config/custom_components/test/lock.py index 4894f00fd75..a6ce9e102d5 100644 --- a/tests/testing_config/custom_components/test/lock.py +++ b/tests/testing_config/custom_components/test/lock.py @@ -19,13 +19,13 @@ def init(empty=False): if empty else { "support_open": MockLock( - name=f"Support open Lock", + name="Support open Lock", is_locked=True, supported_features=SUPPORT_OPEN, unique_id="unique_support_open", ), "no_support_open": MockLock( - name=f"No support open Lock", + name="No support open Lock", is_locked=True, supported_features=0, unique_id="unique_no_support_open", From d4b66717a42dea0a10ddbd39846ee613e191c781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel?= Date: Thu, 30 Apr 2020 17:04:53 -0300 Subject: [PATCH 159/511] Remove panasonic_viera from legacy discovery (#34909) --- homeassistant/components/discovery/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index b9b3f51f60d..227995db971 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -66,7 +66,6 @@ SERVICE_HANDLERS = { SERVICE_OCTOPRINT: ("octoprint", None), SERVICE_FREEBOX: ("freebox", None), SERVICE_YEELIGHT: ("yeelight", None), - "panasonic_viera": ("media_player", "panasonic_viera"), "yamaha": ("media_player", "yamaha"), "logitech_mediaserver": ("media_player", "squeezebox"), "denonavr": ("media_player", "denonavr"), From 6b16c34fd0ba89743ad3caf04fac1a6d62ce121a Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 30 Apr 2020 22:07:07 +0200 Subject: [PATCH 160/511] Improve logging for unregistered webhooks (#34882) * Improve logging for unregistered webhooks * Address comment, KEY_REAL_IP * Address comment, read(64) * Lint --- homeassistant/components/webhook/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 84ebdaddad0..04332166c60 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -7,6 +7,7 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.http.view import HomeAssistantView +from homeassistant.components.http.const import KEY_REAL_IP from homeassistant.const import HTTP_OK from homeassistant.core import callback from homeassistant.loader import bind_hass @@ -71,7 +72,14 @@ async def async_handle_webhook(hass, webhook_id, request): # Always respond successfully to not give away if a hook exists or not. if webhook is None: - _LOGGER.warning("Received message for unregistered webhook %s", webhook_id) + peer_ip = request[KEY_REAL_IP] + _LOGGER.warning( + "Received message for unregistered webhook %s from %s", webhook_id, peer_ip + ) + # 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) return Response(status=HTTP_OK) try: From ec47216388059166c925991cdc6606e6ce8d8c31 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 30 Apr 2020 13:29:50 -0700 Subject: [PATCH 161/511] Use built-in test helpers on 3.8 (#34901) --- .../components/hassio/addon_panel.py | 9 +- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/accessories.py | 2 +- homeassistant/components/smhi/weather.py | 19 +- homeassistant/components/vera/__init__.py | 3 +- .../config_flow/tests/test_config_flow.py | 4 +- .../tests/test_config_flow.py | 4 +- tests/async_mock.py | 8 + tests/auth/providers/test_command_line.py | 5 +- tests/auth/providers/test_homeassistant.py | 3 +- tests/auth/providers/test_insecure_example.py | 5 +- tests/auth/test_auth_store.py | 12 +- tests/common.py | 59 +++--- tests/components/adguard/test_config_flow.py | 16 +- tests/components/airly/test_config_flow.py | 2 +- .../components/airvisual/test_config_flow.py | 2 +- tests/components/almond/test_config_flow.py | 7 +- .../ambiclimate/test_config_flow.py | 17 +- tests/components/arcam_fmj/conftest.py | 3 +- .../components/arcam_fmj/test_media_player.py | 3 +- tests/components/asuswrt/test_sensor.py | 10 +- tests/components/atag/test_config_flow.py | 2 +- tests/components/august/mocks.py | 24 ++- tests/components/august/test_camera.py | 5 +- tests/components/august/test_config_flow.py | 3 +- tests/components/august/test_gateway.py | 11 +- tests/components/august/test_init.py | 2 +- tests/components/auth/test_indieauth.py | 11 +- .../automatic/test_device_tracker.py | 3 +- .../automation/test_homeassistant.py | 8 +- tests/components/awair/test_sensor.py | 3 +- tests/components/aws/test_init.py | 18 +- tests/components/axis/test_config_flow.py | 3 +- tests/components/axis/test_device.py | 2 +- tests/components/axis/test_init.py | 5 +- tests/components/braviatv/test_config_flow.py | 3 +- tests/components/brother/__init__.py | 3 +- tests/components/brother/test_config_flow.py | 2 +- tests/components/brother/test_init.py | 3 +- tests/components/brother/test_sensor.py | 3 +- tests/components/caldav/test_calendar.py | 3 +- tests/components/camera/test_init.py | 2 +- tests/components/cast/test_media_player.py | 2 +- .../cert_expiry/test_config_flow.py | 3 +- tests/components/cert_expiry/test_init.py | 3 +- tests/components/cert_expiry/test_sensors.py | 3 +- tests/components/cloud/test_account_link.py | 4 +- tests/components/cloud/test_alexa_config.py | 16 +- tests/components/cloud/test_binary_sensor.py | 4 +- tests/components/cloud/test_client.py | 3 +- tests/components/cloud/test_google_config.py | 9 +- tests/components/cloud/test_http_api.py | 39 ++-- tests/components/config/test_automation.py | 4 +- tests/components/config/test_core.py | 3 +- tests/components/config/test_customize.py | 4 +- tests/components/config/test_group.py | 6 +- tests/components/config/test_init.py | 14 +- tests/components/config/test_scene.py | 4 +- tests/components/conftest.py | 3 +- .../components/coolmaster/test_config_flow.py | 4 +- tests/components/coronavirus/conftest.py | 3 +- tests/components/deconz/test_climate.py | 4 +- tests/components/deconz/test_cover.py | 4 +- tests/components/deconz/test_deconz_event.py | 31 +--- tests/components/deconz/test_gateway.py | 2 +- tests/components/deconz/test_init.py | 4 +- tests/components/deconz/test_light.py | 4 +- tests/components/deconz/test_scene.py | 4 +- tests/components/deconz/test_services.py | 3 +- tests/components/deconz/test_switch.py | 4 +- tests/components/demo/test_media_player.py | 2 +- .../device_sun_light_trigger/test_init.py | 2 +- tests/components/device_tracker/test_init.py | 2 +- tests/components/directv/test_config_flow.py | 2 +- tests/components/directv/test_media_player.py | 2 +- tests/components/directv/test_remote.py | 3 +- tests/components/discovery/test_init.py | 2 +- tests/components/doorbird/test_config_flow.py | 3 +- tests/components/dsmr/test_sensor.py | 8 +- tests/components/dynalite/common.py | 5 +- tests/components/dynalite/test_bridge.py | 9 +- tests/components/dynalite/test_config_flow.py | 4 +- tests/components/dynalite/test_init.py | 3 +- tests/components/dyson/test_air_quality.py | 23 +-- tests/components/dyson/test_climate.py | 6 +- tests/components/dyson/test_fan.py | 58 +++--- tests/components/dyson/test_sensor.py | 6 +- .../ee_brightbox/test_device_tracker.py | 3 +- tests/components/elkm1/test_config_flow.py | 6 +- tests/components/esphome/test_config_flow.py | 18 +- tests/components/flume/test_config_flow.py | 3 +- .../components/flunearyou/test_config_flow.py | 2 +- tests/components/flux/test_switch.py | 2 +- tests/components/freebox/test_config_flow.py | 12 +- tests/components/frontend/test_init.py | 2 +- tests/components/gdacs/test_config_flow.py | 3 +- tests/components/gdacs/test_geo_location.py | 3 +- tests/components/gdacs/test_init.py | 4 +- tests/components/gdacs/test_sensor.py | 3 +- .../generic_thermostat/test_climate.py | 14 +- .../geo_json_events/test_geo_location.py | 3 +- .../geonetnz_quakes/test_geo_location.py | 3 +- tests/components/geonetnz_quakes/test_init.py | 4 +- .../components/geonetnz_quakes/test_sensor.py | 3 +- .../components/geonetnz_volcano/test_init.py | 6 +- .../geonetnz_volcano/test_sensor.py | 7 +- tests/components/gios/test_config_flow.py | 3 +- tests/components/google_assistant/__init__.py | 4 +- .../google_assistant/test_helpers.py | 2 +- .../components/google_assistant/test_http.py | 4 +- .../google_assistant/test_report_state.py | 15 +- .../google_assistant/test_smart_home.py | 2 +- .../components/google_assistant/test_trait.py | 2 +- tests/components/griddy/test_config_flow.py | 4 +- tests/components/griddy/test_sensor.py | 2 +- tests/components/group/test_light.py | 6 +- tests/components/harmony/test_config_flow.py | 8 +- tests/components/hassio/conftest.py | 9 +- tests/components/hassio/test_addon_panel.py | 6 +- tests/components/hassio/test_auth.py | 11 +- tests/components/hassio/test_discovery.py | 12 +- tests/components/hassio/test_init.py | 3 +- tests/components/heos/conftest.py | 2 +- tests/components/heos/test_config_flow.py | 3 +- tests/components/heos/test_init.py | 3 +- tests/components/hisense_aehw4a1/test_init.py | 3 +- tests/components/homeassistant/test_init.py | 2 +- tests/components/homekit/test_aidmanager.py | 2 +- tests/components/homekit/test_homekit.py | 4 +- tests/components/homekit/test_init.py | 4 +- .../components/homekit_controller/conftest.py | 5 +- .../homekit_controller/test_config_flow.py | 14 +- .../components/homematicip_cloud/conftest.py | 6 +- tests/components/homematicip_cloud/helper.py | 2 +- .../homematicip_cloud/test_config_flow.py | 3 +- .../homematicip_cloud/test_device.py | 3 +- .../components/homematicip_cloud/test_hap.py | 3 +- .../components/homematicip_cloud/test_init.py | 14 +- tests/components/http/test_ban.py | 3 +- tests/components/hue/test_bridge.py | 7 +- tests/components/hue/test_config_flow.py | 12 +- tests/components/hue/test_init.py | 15 +- .../test_config_flow.py | 9 +- .../components/image_processing/test_init.py | 3 +- tests/components/ipma/test_config_flow.py | 17 +- tests/components/ipma/test_weather.py | 3 +- tests/components/ipp/test_sensor.py | 3 +- tests/components/izone/test_config_flow.py | 3 +- .../components/konnected/test_config_flow.py | 2 +- tests/components/konnected/test_init.py | 2 +- tests/components/konnected/test_panel.py | 2 +- tests/components/logbook/test_init.py | 2 +- .../logi_circle/test_config_flow.py | 5 +- tests/components/lovelace/test_resources.py | 4 +- .../components/luftdaten/test_config_flow.py | 3 +- tests/components/media_player/test_init.py | 6 +- tests/components/melcloud/test_config_flow.py | 2 +- tests/components/met/conftest.py | 6 +- tests/components/met/test_config_flow.py | 2 +- tests/components/microsoft_face/test_init.py | 3 +- tests/components/mikrotik/test_hub.py | 2 +- tests/components/mikrotik/test_init.py | 9 +- .../minecraft_server/test_config_flow.py | 2 +- tests/components/minio/test_minio.py | 2 +- .../components/monoprice/test_config_flow.py | 2 +- .../components/monoprice/test_media_player.py | 2 +- tests/components/mqtt/test_device_tracker.py | 2 +- tests/components/mqtt/test_discovery.py | 8 +- tests/components/mqtt/test_init.py | 6 +- tests/components/mqtt/test_light.py | 3 +- tests/components/mqtt/test_light_json.py | 3 +- tests/components/mqtt/test_light_template.py | 3 +- tests/components/mqtt/test_server.py | 14 +- tests/components/mqtt/test_switch.py | 6 +- .../mqtt_json/test_device_tracker.py | 2 +- tests/components/myq/test_config_flow.py | 2 +- tests/components/myq/util.py | 3 +- tests/components/mythicbeastsdns/test_init.py | 10 +- tests/components/ness_alarm/test_init.py | 3 +- tests/components/nest/test_config_flow.py | 15 +- tests/components/netatmo/test_config_flow.py | 3 +- tests/components/nexia/test_config_flow.py | 4 +- tests/components/nexia/util.py | 2 +- tests/components/notion/test_config_flow.py | 14 +- .../test_geo_location.py | 2 +- tests/components/nuheat/mocks.py | 3 +- tests/components/nuheat/test_climate.py | 4 +- tests/components/nuheat/test_config_flow.py | 3 +- tests/components/nut/test_config_flow.py | 3 +- tests/components/nut/util.py | 3 +- tests/components/nws/conftest.py | 12 +- tests/components/nws/test_config_flow.py | 3 +- .../openalpr_cloud/test_image_processing.py | 16 +- .../openalpr_local/test_image_processing.py | 5 +- .../opentherm_gw/test_config_flow.py | 37 ++-- .../components/owntracks/test_config_flow.py | 8 +- .../owntracks/test_device_tracker.py | 2 +- .../panasonic_viera/test_config_flow.py | 2 +- tests/components/person/test_init.py | 2 +- tests/components/pi_hole/test_init.py | 15 +- tests/components/plex/test_config_flow.py | 2 +- tests/components/plex/test_init.py | 138 +++++++------- tests/components/plex/test_server.py | 168 +++++++++--------- tests/components/point/test_config_flow.py | 7 +- tests/components/powerwall/mocks.py | 2 +- .../powerwall/test_binary_sensor.py | 4 +- .../components/powerwall/test_config_flow.py | 3 +- tests/components/powerwall/test_sensor.py | 4 +- tests/components/ps4/conftest.py | 3 +- tests/components/ps4/test_config_flow.py | 2 +- tests/components/ps4/test_init.py | 3 +- tests/components/ps4/test_media_player.py | 2 +- tests/components/ptvsd/test_ptvsd.py | 7 +- tests/components/qwikswitch/test_init.py | 2 +- tests/components/rachio/test_config_flow.py | 4 +- .../rainmachine/test_config_flow.py | 12 +- tests/components/ring/test_config_flow.py | 8 +- tests/components/rmvtransport/test_sensor.py | 4 +- tests/components/roku/test_config_flow.py | 2 +- tests/components/roku/test_init.py | 2 +- tests/components/roku/test_media_player.py | 2 +- tests/components/roomba/test_config_flow.py | 2 +- .../components/samsungtv/test_config_flow.py | 23 ++- tests/components/samsungtv/test_init.py | 10 +- .../components/samsungtv/test_media_player.py | 47 +++-- tests/components/sense/test_config_flow.py | 3 +- tests/components/sentry/test_config_flow.py | 10 +- tests/components/shell_command/test_init.py | 4 +- tests/components/shopping_list/conftest.py | 2 +- .../components/simplisafe/test_config_flow.py | 2 +- tests/components/smartthings/conftest.py | 2 +- .../smartthings/test_config_flow.py | 12 +- tests/components/smartthings/test_init.py | 2 +- tests/components/smartthings/test_smartapp.py | 6 +- tests/components/smhi/test_config_flow.py | 37 ++-- tests/components/smhi/test_weather.py | 23 +-- tests/components/solarlog/test_config_flow.py | 13 +- tests/components/sonos/conftest.py | 2 +- .../soundtouch/test_media_player.py | 3 +- tests/components/stream/test_init.py | 4 +- tests/components/switcher_kis/conftest.py | 9 +- tests/components/tado/test_config_flow.py | 2 +- tests/components/tesla/test_config_flow.py | 14 +- tests/components/tradfri/conftest.py | 3 +- tests/components/tradfri/test_config_flow.py | 2 +- tests/components/tradfri/test_init.py | 3 +- tests/components/uk_transport/test_sensor.py | 2 +- tests/components/unifi/conftest.py | 3 +- tests/components/unifi/test_config_flow.py | 2 +- tests/components/unifi/test_controller.py | 2 +- tests/components/unifi/test_device_tracker.py | 3 +- tests/components/unifi/test_init.py | 5 +- .../unifi_direct/test_device_tracker.py | 6 +- tests/components/updater/test_init.py | 16 +- tests/components/upnp/test_init.py | 11 +- tests/components/vera/test_init.py | 2 +- tests/components/vilfo/test_config_flow.py | 5 +- tests/components/vizio/conftest.py | 3 +- tests/components/vizio/test_media_player.py | 2 +- tests/components/webostv/test_media_player.py | 2 +- tests/components/websocket_api/test_http.py | 2 +- tests/components/withings/test_common.py | 3 +- tests/components/withings/test_init.py | 3 +- tests/components/wled/test_light.py | 2 +- tests/components/wled/test_sensor.py | 2 +- tests/components/wled/test_switch.py | 2 +- tests/components/wwlln/test_config_flow.py | 3 +- .../components/xiaomi/test_device_tracker.py | 11 +- .../xiaomi_miio/test_config_flow.py | 3 +- tests/components/zeroconf/test_init.py | 3 +- tests/components/zha/common.py | 19 +- tests/components/zha/conftest.py | 12 +- tests/components/zha/test_channels.py | 17 +- tests/components/zha/test_config_flow.py | 27 ++- tests/components/zha/test_cover.py | 6 +- tests/components/zha/test_device.py | 8 +- tests/components/zha/test_discover.py | 9 +- tests/components/zha/test_light.py | 10 +- tests/components/zone/test_init.py | 2 +- tests/components/zwave/test_init.py | 14 +- tests/conftest.py | 3 +- tests/helpers/test_area_registry.py | 4 +- tests/helpers/test_config_entry_flow.py | 2 +- tests/helpers/test_debounce.py | 8 +- tests/helpers/test_device_registry.py | 4 +- tests/helpers/test_entity_component.py | 23 +-- tests/helpers/test_entity_platform.py | 7 +- tests/helpers/test_entity_registry.py | 4 +- tests/helpers/test_restore_state.py | 8 +- tests/helpers/test_script.py | 4 +- tests/helpers/test_service.py | 12 +- tests/helpers/test_storage.py | 2 +- tests/helpers/test_translation.py | 34 ++-- tests/helpers/test_update_coordinator.py | 6 +- tests/test_bootstrap.py | 2 +- tests/test_config.py | 46 +++-- tests/test_config_entries.py | 109 ++++++------ tests/test_core.py | 2 +- tests/test_loader.py | 2 +- tests/test_requirements.py | 79 +++----- tests/test_setup.py | 2 +- tests/util/test_location.py | 2 +- tests/util/test_package.py | 7 +- 303 files changed, 1163 insertions(+), 1320 deletions(-) create mode 100644 tests/async_mock.py diff --git a/homeassistant/components/hassio/addon_panel.py b/homeassistant/components/hassio/addon_panel.py index d8616a82578..9e44b961a1c 100644 --- a/homeassistant/components/hassio/addon_panel.py +++ b/homeassistant/components/hassio/addon_panel.py @@ -75,12 +75,9 @@ class HassIOAddonPanel(HomeAssistantView): return {} -def _register_panel(hass, addon, data): - """Init coroutine to register the panel. - - Return coroutine. - """ - return hass.components.panel_custom.async_register_panel( +async def _register_panel(hass, addon, data): + """Init coroutine to register the panel.""" + await hass.components.panel_custom.async_register_panel( frontend_url_path=addon, webcomponent_name="hassio-main", sidebar_title=data[ATTR_TITLE], diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 35620e9e11d..479e1e4dab9 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -484,7 +484,7 @@ class HomeKit: ) _LOGGER.debug("Driver start") - self.hass.async_add_executor_job(self.driver.start) + self.hass.add_job(self.driver.start) self.status = STATUS_RUNNING async def async_stop(self, *args): diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 4f0a840770c..ab3d1ae8507 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -165,7 +165,7 @@ class HomeAccessory(Accessory): Run inside the Home Assistant event loop. """ state = self.hass.states.get(self.entity_id) - self.hass.async_add_executor_job(self.update_state_callback, None, None, state) + self.hass.async_add_job(self.update_state_callback, None, None, state) async_track_state_change(self.hass, self.entity_id, self.update_state_callback) battery_charging_state = None diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 0c5450b5ddd..f09491bf611 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -99,15 +99,6 @@ class SmhiWeather(WeatherEntity): @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self) -> None: """Refresh the forecast data from SMHI weather API.""" - - def fail(): - """Postpone updates.""" - self._fail_count += 1 - if self._fail_count < 3: - self.hass.helpers.event.async_call_later( - RETRY_TIMEOUT, self.retry_update() - ) - try: with async_timeout.timeout(10): self._forecasts = await self.get_weather_forecast() @@ -115,11 +106,15 @@ class SmhiWeather(WeatherEntity): except (asyncio.TimeoutError, SmhiForecastException): _LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes") - fail() + self._fail_count += 1 + if self._fail_count < 3: + self.hass.helpers.event.async_call_later( + RETRY_TIMEOUT, self.retry_update + ) - async def retry_update(self): + async def retry_update(self, _): """Retry refresh weather forecast.""" - self.async_update() + await self.async_update() async def get_weather_forecast(self) -> []: """Return the current forecasts from SMHI API.""" diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index c98833a7daa..9b2d44fd94b 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -90,8 +90,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b controller.start() hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, - lambda event: hass.async_add_executor_job(controller.stop), + EVENT_HOMEASSISTANT_STOP, lambda event: controller.stop() ) try: 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 3d829b5cc32..d6dee8d0bd0 100644 --- a/script/scaffold/templates/config_flow/tests/test_config_flow.py +++ b/script/scaffold/templates/config_flow/tests/test_config_flow.py @@ -1,10 +1,10 @@ """Test the NEW_NAME config flow.""" -from asynctest 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 tests.async_mock import patch + async def test_form(hass): """Test we get the form.""" 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 8a543a04af3..7dda6564507 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 @@ -1,6 +1,4 @@ """Test the NEW_NAME config flow.""" -from asynctest import patch - from homeassistant import config_entries, setup from homeassistant.components.NEW_DOMAIN.const import ( DOMAIN, @@ -9,6 +7,8 @@ from homeassistant.components.NEW_DOMAIN.const import ( ) from homeassistant.helpers import config_entry_oauth2_flow +from tests.async_mock import patch + CLIENT_ID = "1234" CLIENT_SECRET = "5678" diff --git a/tests/async_mock.py b/tests/async_mock.py new file mode 100644 index 00000000000..1942b2ca284 --- /dev/null +++ b/tests/async_mock.py @@ -0,0 +1,8 @@ +"""Mock utilities that are async aware.""" +import sys + +if sys.version_info[:2] < (3, 8): + from asynctest.mock import * # noqa + from asynctest.mock import CoroutineMock as AsyncMock # noqa +else: + from unittest.mock import * # noqa diff --git a/tests/auth/providers/test_command_line.py b/tests/auth/providers/test_command_line.py index abcf124b9c4..3915950cedb 100644 --- a/tests/auth/providers/test_command_line.py +++ b/tests/auth/providers/test_command_line.py @@ -1,7 +1,6 @@ """Tests for the command_line auth provider.""" import os -from unittest.mock import Mock import uuid import pytest @@ -11,7 +10,7 @@ from homeassistant.auth import AuthManager, auth_store, models as auth_models from homeassistant.auth.providers import command_line from homeassistant.const import CONF_TYPE -from tests.common import mock_coro +from tests.async_mock import AsyncMock @pytest.fixture @@ -63,7 +62,7 @@ async def test_match_existing_credentials(store, provider): data={"username": "good-user"}, is_new=False, ) - provider.async_credentials = Mock(return_value=mock_coro([existing])) + provider.async_credentials = AsyncMock(return_value=[existing]) credentials = await provider.async_get_or_create_credentials( {"username": "good-user", "password": "irrelevant"} ) diff --git a/tests/auth/providers/test_homeassistant.py b/tests/auth/providers/test_homeassistant.py index 9cfdfc30aa5..f9ac8fbb92a 100644 --- a/tests/auth/providers/test_homeassistant.py +++ b/tests/auth/providers/test_homeassistant.py @@ -1,7 +1,6 @@ """Test the Home Assistant local auth provider.""" import asyncio -from asynctest import Mock, patch import pytest import voluptuous as vol @@ -12,6 +11,8 @@ from homeassistant.auth.providers import ( homeassistant as hass_auth, ) +from tests.async_mock import Mock, patch + @pytest.fixture def data(hass): diff --git a/tests/auth/providers/test_insecure_example.py b/tests/auth/providers/test_insecure_example.py index c5b3a8db038..c2b16cbafab 100644 --- a/tests/auth/providers/test_insecure_example.py +++ b/tests/auth/providers/test_insecure_example.py @@ -1,5 +1,4 @@ """Tests for the insecure example auth provider.""" -from unittest.mock import Mock import uuid import pytest @@ -7,7 +6,7 @@ import pytest from homeassistant.auth import AuthManager, auth_store, models as auth_models from homeassistant.auth.providers import insecure_example -from tests.common import mock_coro +from tests.async_mock import AsyncMock @pytest.fixture @@ -63,7 +62,7 @@ async def test_match_existing_credentials(store, provider): data={"username": "user-test"}, is_new=False, ) - provider.async_credentials = Mock(return_value=mock_coro([existing])) + provider.async_credentials = AsyncMock(return_value=[existing]) credentials = await provider.async_get_or_create_credentials( {"username": "user-test", "password": "password-test"} ) diff --git a/tests/auth/test_auth_store.py b/tests/auth/test_auth_store.py index 109eb20fb6c..78ab9829ab6 100644 --- a/tests/auth/test_auth_store.py +++ b/tests/auth/test_auth_store.py @@ -1,10 +1,10 @@ """Tests for the auth store.""" import asyncio -import asynctest - from homeassistant.auth import auth_store +from tests.async_mock import patch + async def test_loading_no_group_data_format(hass, hass_storage): """Test we correctly load old data without any groups.""" @@ -229,12 +229,12 @@ async def test_system_groups_store_id_and_name(hass, hass_storage): async def test_loading_race_condition(hass): """Test only one storage load called when concurrent loading occurred .""" store = auth_store.AuthStore(hass) - with asynctest.patch( + with patch( "homeassistant.helpers.entity_registry.async_get_registry" - ) as mock_ent_registry, asynctest.patch( + ) as mock_ent_registry, patch( "homeassistant.helpers.device_registry.async_get_registry" - ) as mock_dev_registry, asynctest.patch( - "homeassistant.helpers.storage.Store.async_load" + ) as mock_dev_registry, patch( + "homeassistant.helpers.storage.Store.async_load", return_value=None ) as mock_load: results = await asyncio.gather(store.async_get_users(), store.async_get_users()) diff --git a/tests/common.py b/tests/common.py index 5a8250e5686..b76fa23bbf2 100644 --- a/tests/common.py +++ b/tests/common.py @@ -14,7 +14,6 @@ import threading import uuid from aiohttp.test_utils import unused_port as get_test_instance_port # noqa -from asynctest import MagicMock, Mock, patch from homeassistant import auth, config_entries, core as ha, loader from homeassistant.auth import ( @@ -60,6 +59,8 @@ import homeassistant.util.dt as date_util from homeassistant.util.unit_system import METRIC_SYSTEM import homeassistant.util.yaml.loader as yaml_loader +from tests.async_mock import AsyncMock, MagicMock, Mock, patch + _LOGGER = logging.getLogger(__name__) INSTANCES = [] CLIENT_ID = "https://example.com/app" @@ -159,20 +160,37 @@ async def async_test_home_assistant(loop): def async_add_job(target, *args): """Add job.""" - if isinstance(target, Mock): - return mock_coro(target(*args)) + check_target = target + while isinstance(check_target, ft.partial): + check_target = check_target.func + + if isinstance(check_target, Mock) and not isinstance(target, AsyncMock): + fut = asyncio.Future() + fut.set_result(target(*args)) + return fut + return orig_async_add_job(target, *args) def async_add_executor_job(target, *args): """Add executor job.""" - if isinstance(target, Mock): - return mock_coro(target(*args)) + check_target = target + while isinstance(check_target, ft.partial): + check_target = check_target.func + + if isinstance(check_target, Mock): + fut = asyncio.Future() + fut.set_result(target(*args)) + return fut + return orig_async_add_executor_job(target, *args) def async_create_task(coroutine): """Create task.""" - if isinstance(coroutine, Mock): - return mock_coro() + if isinstance(coroutine, Mock) and not isinstance(coroutine, AsyncMock): + fut = asyncio.Future() + fut.set_result(None) + return fut + return orig_async_create_task(coroutine) hass.async_add_job = async_add_job @@ -311,15 +329,16 @@ async def async_mock_mqtt_component(hass, config=None): if config is None: config = {mqtt.CONF_BROKER: "mock-broker"} - async def _async_fire_mqtt_message(topic, payload, qos, retain): + @ha.callback + def _async_fire_mqtt_message(topic, payload, qos, retain): async_fire_mqtt_message(hass, topic, payload, qos, retain) with patch("paho.mqtt.client.Client") as mock_client: - mock_client().connect.return_value = 0 - mock_client().subscribe.return_value = (0, 0) - mock_client().unsubscribe.return_value = (0, 0) - mock_client().publish.return_value = (0, 0) - mock_client().publish.side_effect = _async_fire_mqtt_message + mock_client = mock_client.return_value + mock_client.connect.return_value = 0 + mock_client.subscribe.return_value = (0, 0) + mock_client.unsubscribe.return_value = (0, 0) + mock_client.publish.side_effect = _async_fire_mqtt_message result = await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) assert result @@ -503,7 +522,7 @@ class MockModule: self.async_setup = async_setup if setup is None and async_setup is None: - self.async_setup = mock_coro_func(True) + self.async_setup = AsyncMock(return_value=True) if async_setup_entry is not None: self.async_setup_entry = async_setup_entry @@ -561,7 +580,7 @@ class MockPlatform: self.async_setup_entry = async_setup_entry if setup_platform is None and async_setup_platform is None: - self.async_setup_platform = mock_coro_func() + self.async_setup_platform = AsyncMock(return_value=None) class MockEntityPlatform(entity_platform.EntityPlatform): @@ -731,14 +750,10 @@ def mock_coro(return_value=None, exception=None): def mock_coro_func(return_value=None, exception=None): """Return a method to create a coro function that returns a value.""" - @asyncio.coroutine - def coro(*args, **kwargs): - """Fake coroutine.""" - if exception: - raise exception - return return_value + if exception: + return AsyncMock(side_effect=exception) - return coro + return AsyncMock(return_value=return_value) @contextmanager diff --git a/tests/components/adguard/test_config_flow.py b/tests/components/adguard/test_config_flow.py index a0d575deac0..d0e874bacdc 100644 --- a/tests/components/adguard/test_config_flow.py +++ b/tests/components/adguard/test_config_flow.py @@ -1,5 +1,4 @@ """Tests for the AdGuard Home config flow.""" -from unittest.mock import patch import aiohttp @@ -15,7 +14,8 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry FIXTURE_USER_INPUT = { CONF_HOST: "127.0.0.1", @@ -156,22 +156,16 @@ async def test_hassio_update_instance_running(hass, aioclient_mock): entry.add_to_hass(hass) with patch.object( - hass.config_entries, - "async_forward_entry_setup", - side_effect=lambda *_: mock_coro(True), + hass.config_entries, "async_forward_entry_setup", return_value=True, ) as mock_load: assert await hass.config_entries.async_setup(entry.entry_id) assert entry.state == config_entries.ENTRY_STATE_LOADED assert len(mock_load.mock_calls) == 2 with patch.object( - hass.config_entries, - "async_forward_entry_unload", - side_effect=lambda *_: mock_coro(True), + hass.config_entries, "async_forward_entry_unload", return_value=True, ) as mock_unload, patch.object( - hass.config_entries, - "async_forward_entry_setup", - side_effect=lambda *_: mock_coro(True), + hass.config_entries, "async_forward_entry_setup", return_value=True, ) as mock_load: result = await hass.config_entries.flow.async_init( "adguard", diff --git a/tests/components/airly/test_config_flow.py b/tests/components/airly/test_config_flow.py index 83e7d5d210e..243a92258eb 100644 --- a/tests/components/airly/test_config_flow.py +++ b/tests/components/airly/test_config_flow.py @@ -2,7 +2,6 @@ import json from airly.exceptions import AirlyError -from asynctest import patch from homeassistant import data_entry_flow from homeassistant.components.airly.const import DOMAIN @@ -15,6 +14,7 @@ from homeassistant.const import ( HTTP_FORBIDDEN, ) +from tests.async_mock import patch from tests.common import MockConfigEntry, load_fixture CONFIG = { diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index 9127e6b2780..fcb2360b6c6 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -1,5 +1,4 @@ """Define tests for the AirVisual config flow.""" -from asynctest import patch from pyairvisual.errors import InvalidKeyError, NodeProError from homeassistant import data_entry_flow @@ -21,6 +20,7 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/almond/test_config_flow.py b/tests/components/almond/test_config_flow.py index 0b4869ee2a6..d1403c017aa 100644 --- a/tests/components/almond/test_config_flow.py +++ b/tests/components/almond/test_config_flow.py @@ -1,14 +1,13 @@ """Test the Almond config flow.""" import asyncio -from asynctest import patch - from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.almond import config_flow from homeassistant.components.almond.const import DOMAIN from homeassistant.helpers import config_entry_oauth2_flow -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry CLIENT_ID_VALUE = "1234" CLIENT_SECRET_VALUE = "5678" @@ -16,7 +15,7 @@ CLIENT_SECRET_VALUE = "5678" async def test_import(hass): """Test that we can import a config entry.""" - with patch("pyalmond.WebAlmondAPI.async_list_apps", side_effect=mock_coro): + with patch("pyalmond.WebAlmondAPI.async_list_apps"): assert await setup.async_setup_component( hass, "almond", diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index 045eac1328b..6dee15c27f9 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -1,13 +1,12 @@ """Tests for the Ambiclimate config flow.""" import ambiclimate -from asynctest import Mock, patch from homeassistant import data_entry_flow from homeassistant.components.ambiclimate import config_flow from homeassistant.setup import async_setup_component from homeassistant.util import aiohttp -from tests.common import mock_coro +from tests.async_mock import AsyncMock, patch async def init_config_flow(hass): @@ -67,9 +66,7 @@ async def test_full_flow_implementation(hass): assert "response_type=code" in url assert "redirect_uri=https%3A%2F%2Fhass.com%2Fapi%2Fambiclimate" in url - with patch( - "ambiclimate.AmbiclimateOAuth.get_access_token", return_value=mock_coro("test") - ): + with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value="test"): result = await flow.async_step_code("123ABC") assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Ambiclimate" @@ -77,9 +74,7 @@ async def test_full_flow_implementation(hass): assert result["data"]["client_secret"] == "secret" assert result["data"]["client_id"] == "id" - with patch( - "ambiclimate.AmbiclimateOAuth.get_access_token", return_value=mock_coro(None) - ): + with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value=None): result = await flow.async_step_code("123ABC") assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -96,9 +91,7 @@ async def test_abort_invalid_code(hass): config_flow.register_flow_implementation(hass, None, None) flow = await init_config_flow(hass) - with patch( - "ambiclimate.AmbiclimateOAuth.get_access_token", return_value=mock_coro(None) - ): + with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value=None): result = await flow.async_step_code("invalid") assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "access_token" @@ -118,7 +111,7 @@ async def test_already_setup(hass): async def test_view(hass): """Test view.""" - hass.config_entries.flow.async_init = Mock() + hass.config_entries.flow.async_init = AsyncMock() request = aiohttp.MockRequest(b"", query_string="code=test_code") request.app = {"hass": hass} diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py index ec9c6bb1f36..e515b71468b 100644 --- a/tests/components/arcam_fmj/conftest.py +++ b/tests/components/arcam_fmj/conftest.py @@ -1,7 +1,6 @@ """Tests for the arcam_fmj component.""" from arcam.fmj.client import Client from arcam.fmj.state import State -from asynctest import Mock import pytest from homeassistant.components.arcam_fmj import DEVICE_SCHEMA @@ -9,6 +8,8 @@ from homeassistant.components.arcam_fmj.const import DOMAIN from homeassistant.components.arcam_fmj.media_player import ArcamFmj from homeassistant.const import CONF_HOST, CONF_PORT +from tests.async_mock import Mock + MOCK_HOST = "127.0.0.1" MOCK_PORT = 1234 MOCK_TURN_ON = { diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index a6b36a71d1c..5a73e770129 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -2,7 +2,6 @@ from math import isclose from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes -from asynctest.mock import ANY, MagicMock, Mock, PropertyMock, patch import pytest from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC @@ -10,6 +9,8 @@ from homeassistant.core import HomeAssistant from .conftest import MOCK_ENTITY_ID, MOCK_HOST, MOCK_NAME, MOCK_PORT +from tests.async_mock import ANY, MagicMock, Mock, PropertyMock, patch + MOCK_TURN_ON = { "service": "switch.turn_on", "data": {"entity_id": "switch.test"}, diff --git a/tests/components/asuswrt/test_sensor.py b/tests/components/asuswrt/test_sensor.py index 991be0bac50..6d58b909280 100644 --- a/tests/components/asuswrt/test_sensor.py +++ b/tests/components/asuswrt/test_sensor.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from aioasuswrt.asuswrt import Device -from asynctest import CoroutineMock, patch from homeassistant.components import sensor from homeassistant.components.asuswrt import ( @@ -20,6 +19,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.dt import utcnow +from tests.async_mock import AsyncMock, patch from tests.common import async_fire_time_changed VALID_CONFIG_ROUTER_SSH = { @@ -54,10 +54,10 @@ MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000] async def test_sensors(hass: HomeAssistant): """Test creating an AsusWRT sensor.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = CoroutineMock() - AsusWrt().async_get_connected_devices = CoroutineMock(return_value=MOCK_DEVICES) - AsusWrt().async_get_bytes_total = CoroutineMock(return_value=MOCK_BYTES_TOTAL) - AsusWrt().async_get_current_transfer_rates = CoroutineMock( + AsusWrt().connection.async_connect = AsyncMock() + AsusWrt().async_get_connected_devices = AsyncMock(return_value=MOCK_DEVICES) + AsusWrt().async_get_bytes_total = AsyncMock(return_value=MOCK_BYTES_TOTAL) + AsusWrt().async_get_current_transfer_rates = AsyncMock( return_value=MOCK_CURRENT_TRANSFER_RATES ) diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index bda4ccc9023..c860b85c240 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,13 +1,13 @@ """Tests for the Atag config flow.""" from unittest.mock import PropertyMock -from asynctest import patch from pyatag import AtagException from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_PORT +from tests.async_mock import patch from tests.common import MockConfigEntry FIXTURE_USER_INPUT = { diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 39b18411d66..0e1e9866c1d 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -3,8 +3,6 @@ import json import os import time -from asynctest import mock -from asynctest.mock import CoroutineMock, MagicMock, PropertyMock from august.activity import ( ACTIVITY_ACTIONS_DOOR_OPERATION, ACTIVITY_ACTIONS_DOORBELL_DING, @@ -29,6 +27,8 @@ from homeassistant.components.august import ( ) from homeassistant.setup import async_setup_component +# from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, MagicMock, PropertyMock, patch from tests.common import load_fixture @@ -43,10 +43,8 @@ def _mock_get_config(): } -@mock.patch("homeassistant.components.august.gateway.ApiAsync") -@mock.patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate" -) +@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): """Set up august integration.""" authenticate_mock.side_effect = MagicMock( @@ -150,37 +148,37 @@ async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): api_instance = MagicMock(name="Api") if api_call_side_effects["get_lock_detail"]: - type(api_instance).async_get_lock_detail = CoroutineMock( + type(api_instance).async_get_lock_detail = AsyncMock( side_effect=api_call_side_effects["get_lock_detail"] ) if api_call_side_effects["get_operable_locks"]: - type(api_instance).async_get_operable_locks = CoroutineMock( + type(api_instance).async_get_operable_locks = AsyncMock( side_effect=api_call_side_effects["get_operable_locks"] ) if api_call_side_effects["get_doorbells"]: - type(api_instance).async_get_doorbells = CoroutineMock( + type(api_instance).async_get_doorbells = AsyncMock( side_effect=api_call_side_effects["get_doorbells"] ) if api_call_side_effects["get_doorbell_detail"]: - type(api_instance).async_get_doorbell_detail = CoroutineMock( + type(api_instance).async_get_doorbell_detail = AsyncMock( side_effect=api_call_side_effects["get_doorbell_detail"] ) if api_call_side_effects["get_house_activities"]: - type(api_instance).async_get_house_activities = CoroutineMock( + type(api_instance).async_get_house_activities = AsyncMock( side_effect=api_call_side_effects["get_house_activities"] ) if api_call_side_effects["lock_return_activities"]: - type(api_instance).async_lock_return_activities = CoroutineMock( + type(api_instance).async_lock_return_activities = AsyncMock( side_effect=api_call_side_effects["lock_return_activities"] ) if api_call_side_effects["unlock_return_activities"]: - type(api_instance).async_unlock_return_activities = CoroutineMock( + type(api_instance).async_unlock_return_activities = AsyncMock( side_effect=api_call_side_effects["unlock_return_activities"] ) diff --git a/tests/components/august/test_camera.py b/tests/components/august/test_camera.py index e47bafece42..3ec1b2d608c 100644 --- a/tests/components/august/test_camera.py +++ b/tests/components/august/test_camera.py @@ -1,9 +1,8 @@ """The camera tests for the august platform.""" -from asynctest import mock - from homeassistant.const import STATE_IDLE +from tests.async_mock import patch from tests.components.august.mocks import ( _create_august_with_devices, _mock_doorbell_from_fixture, @@ -14,7 +13,7 @@ async def test_create_doorbell(hass, aiohttp_client): """Test creation of a doorbell.""" doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") - with mock.patch.object( + with patch.object( doorbell_one, "async_get_doorbell_image", create=False, return_value="image" ): await _create_august_with_devices(hass, [doorbell_one]) diff --git a/tests/components/august/test_config_flow.py b/tests/components/august/test_config_flow.py index 8d29ba650fa..ed75bc3685c 100644 --- a/tests/components/august/test_config_flow.py +++ b/tests/components/august/test_config_flow.py @@ -1,5 +1,4 @@ """Test the August config flow.""" -from asynctest import patch from august.authenticator import ValidationResult from homeassistant import config_entries, setup @@ -17,6 +16,8 @@ from homeassistant.components.august.exceptions import ( ) from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +from tests.async_mock import patch + async def test_form(hass): """Test we get the form.""" diff --git a/tests/components/august/test_gateway.py b/tests/components/august/test_gateway.py index f5fe35b4b19..b9d1959d18a 100644 --- a/tests/components/august/test_gateway.py +++ b/tests/components/august/test_gateway.py @@ -1,11 +1,10 @@ """The gateway tests for the august platform.""" from unittest.mock import MagicMock -from asynctest import mock - from homeassistant.components.august.const import DOMAIN from homeassistant.components.august.gateway import AugustGateway +from tests.async_mock import patch from tests.components.august.mocks import _mock_august_authentication, _mock_get_config @@ -14,11 +13,9 @@ async def test_refresh_access_token(hass): await _patched_refresh_access_token(hass, "new_token", 5678) -@mock.patch( - "homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate" -) -@mock.patch("homeassistant.components.august.gateway.AuthenticatorAsync.should_refresh") -@mock.patch( +@patch("homeassistant.components.august.gateway.AuthenticatorAsync.async_authenticate") +@patch("homeassistant.components.august.gateway.AuthenticatorAsync.should_refresh") +@patch( "homeassistant.components.august.gateway.AuthenticatorAsync.async_refresh_access_token" ) async def _patched_refresh_access_token( diff --git a/tests/components/august/test_init.py b/tests/components/august/test_init.py index c287a26b34f..f29403c9f21 100644 --- a/tests/components/august/test_init.py +++ b/tests/components/august/test_init.py @@ -1,7 +1,6 @@ """The tests for the august platform.""" import asyncio -from asynctest import patch from august.exceptions import AugustApiAIOHTTPError from homeassistant import setup @@ -27,6 +26,7 @@ from homeassistant.const import ( from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry from tests.components.august.mocks import ( _create_august_with_devices, diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py index b359144ab97..3d1ba068c85 100644 --- a/tests/components/auth/test_indieauth.py +++ b/tests/components/auth/test_indieauth.py @@ -1,12 +1,11 @@ """Tests for the client validator.""" import asyncio -from unittest.mock import patch import pytest from homeassistant.components.auth import indieauth -from tests.common import mock_coro +from tests.async_mock import patch from tests.test_util.aiohttp import AiohttpClientMocker @@ -113,9 +112,7 @@ async def test_verify_redirect_uri(): None, "http://ex.com", "http://ex.com/callback" ) - with patch.object( - indieauth, "fetch_redirect_uris", side_effect=lambda *_: mock_coro([]) - ): + with patch.object(indieauth, "fetch_redirect_uris", return_value=[]): # Different domain assert not await indieauth.verify_redirect_uri( None, "http://ex.com", "http://different.com/callback" @@ -174,9 +171,7 @@ async def test_find_link_tag_max_size(hass, mock_session): ) async def test_verify_redirect_uri_android_ios(client_id): """Test that we verify redirect uri correctly for Android/iOS.""" - with patch.object( - indieauth, "fetch_redirect_uris", side_effect=lambda *_: mock_coro([]) - ): + with patch.object(indieauth, "fetch_redirect_uris", return_value=[]): assert await indieauth.verify_redirect_uri( None, client_id, "homeassistant://auth-callback" ) diff --git a/tests/components/automatic/test_device_tracker.py b/tests/components/automatic/test_device_tracker.py index 09ea7c61858..492421a77cc 100644 --- a/tests/components/automatic/test_device_tracker.py +++ b/tests/components/automatic/test_device_tracker.py @@ -3,11 +3,12 @@ from datetime import datetime import logging import aioautomatic -from asynctest import MagicMock, patch from homeassistant.components.automatic.device_tracker import async_setup_scanner from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py index a0985e54976..b0db66e16a9 100644 --- a/tests/components/automation/test_homeassistant.py +++ b/tests/components/automation/test_homeassistant.py @@ -1,11 +1,12 @@ """The tests for the Event automation.""" -from unittest.mock import Mock, patch +from unittest.mock import patch import homeassistant.components.automation as automation from homeassistant.core import CoreState from homeassistant.setup import async_setup_component -from tests.common import async_mock_service, mock_coro +from tests.async_mock import AsyncMock +from tests.common import async_mock_service async def test_if_fires_on_hass_start(hass): @@ -30,8 +31,7 @@ async def test_if_fires_on_hass_start(hass): assert len(calls) == 1 with patch( - "homeassistant.config.async_hass_config_yaml", - Mock(return_value=mock_coro(config)), + "homeassistant.config.async_hass_config_yaml", AsyncMock(return_value=config), ): await hass.services.async_call( automation.DOMAIN, automation.SERVICE_RELOAD, blocking=True diff --git a/tests/components/awair/test_sensor.py b/tests/components/awair/test_sensor.py index 8ae5bb8017f..d1a3b933d05 100644 --- a/tests/components/awair/test_sensor.py +++ b/tests/components/awair/test_sensor.py @@ -5,8 +5,6 @@ from datetime import timedelta import json import logging -from asynctest import patch - from homeassistant.components.awair.sensor import ( ATTR_LAST_API_UPDATE, ATTR_TIMESTAMP, @@ -29,6 +27,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from homeassistant.util.dt import parse_datetime, utcnow +from tests.async_mock import patch from tests.common import async_fire_time_changed, load_fixture DISCOVERY_CONFIG = {"sensor": {"platform": "awair", "access_token": "qwerty"}} diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py index c7fa9d0a5c1..045ad2ff609 100644 --- a/tests/components/aws/test_init.py +++ b/tests/components/aws/test_init.py @@ -1,32 +1,32 @@ """Tests for the aws component config and setup.""" -from asynctest import CoroutineMock, MagicMock, patch as async_patch - from homeassistant.components import aws from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock, MagicMock, patch as async_patch + class MockAioSession: """Mock AioSession.""" def __init__(self, *args, **kwargs): """Init a mock session.""" - self.get_user = CoroutineMock() - self.invoke = CoroutineMock() - self.publish = CoroutineMock() - self.send_message = CoroutineMock() + self.get_user = AsyncMock() + self.invoke = AsyncMock() + self.publish = AsyncMock() + self.send_message = AsyncMock() def create_client(self, *args, **kwargs): # pylint: disable=no-self-use """Create a mocked client.""" return MagicMock( - __aenter__=CoroutineMock( - return_value=CoroutineMock( + __aenter__=AsyncMock( + return_value=AsyncMock( get_user=self.get_user, # iam invoke=self.invoke, # lambda publish=self.publish, # sns send_message=self.send_message, # sqs ) ), - __aexit__=CoroutineMock(), + __aexit__=AsyncMock(), ) diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index e0bf06e3468..d3972332c89 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,11 +1,10 @@ """Test Axis config flow.""" -from asynctest import Mock, patch - from homeassistant.components import axis from homeassistant.components.axis import config_flow from .test_device import MAC, MODEL, NAME, setup_axis_integration +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 3d2ed432c1c..74b0ab3b992 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -1,13 +1,13 @@ """Test Axis device.""" from copy import deepcopy -from asynctest import Mock, patch import axis as axislib import pytest from homeassistant import config_entries from homeassistant.components import axis +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry MAC = "00408C12345" diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index d11f8c91fc3..5803f31d526 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -6,7 +6,8 @@ from homeassistant.setup import async_setup_component from .test_device import MAC, setup_axis_integration -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import AsyncMock +from tests.common import MockConfigEntry async def test_setup_no_config(hass): @@ -30,7 +31,7 @@ async def test_setup_entry_fails(hass): config_entry.add_to_hass(hass) mock_device = Mock() - mock_device.async_setup.return_value = mock_coro(False) + mock_device.async_setup = AsyncMock(return_value=False) with patch.object(axis, "AxisNetworkDevice") as mock_device_class: mock_device_class.return_value = mock_device diff --git a/tests/components/braviatv/test_config_flow.py b/tests/components/braviatv/test_config_flow.py index 93d1bccba73..87fa26c242a 100644 --- a/tests/components/braviatv/test_config_flow.py +++ b/tests/components/braviatv/test_config_flow.py @@ -1,11 +1,10 @@ """Define tests for the Bravia TV config flow.""" -from asynctest import patch - from homeassistant import data_entry_flow from homeassistant.components.braviatv.const import CONF_IGNORED_SOURCES, DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PIN +from tests.async_mock import patch from tests.common import MockConfigEntry BRAVIA_SYSTEM_INFO = { diff --git a/tests/components/brother/__init__.py b/tests/components/brother/__init__.py index d6c1fedd31d..1a3ba2a3e20 100644 --- a/tests/components/brother/__init__.py +++ b/tests/components/brother/__init__.py @@ -1,11 +1,10 @@ """Tests for Brother Printer integration.""" import json -from asynctest import patch - from homeassistant.components.brother.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_TYPE +from tests.async_mock import patch from tests.common import MockConfigEntry, load_fixture diff --git a/tests/components/brother/test_config_flow.py b/tests/components/brother/test_config_flow.py index 3f07fca49f0..06e58b83522 100644 --- a/tests/components/brother/test_config_flow.py +++ b/tests/components/brother/test_config_flow.py @@ -1,7 +1,6 @@ """Define tests for the Brother Printer config flow.""" import json -from asynctest import patch from brother import SnmpError, UnsupportedModel from homeassistant import data_entry_flow @@ -9,6 +8,7 @@ from homeassistant.components.brother.const import DOMAIN from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_TYPE +from tests.async_mock import patch from tests.common import MockConfigEntry, load_fixture CONFIG = {CONF_HOST: "localhost", CONF_TYPE: "laser"} diff --git a/tests/components/brother/test_init.py b/tests/components/brother/test_init.py index 13378e9dbb9..04c3c130fc9 100644 --- a/tests/components/brother/test_init.py +++ b/tests/components/brother/test_init.py @@ -1,6 +1,4 @@ """Test init of Brother integration.""" -from asynctest import patch - from homeassistant.components.brother.const import DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_LOADED, @@ -9,6 +7,7 @@ from homeassistant.config_entries import ( ) from homeassistant.const import CONF_HOST, CONF_TYPE, STATE_UNAVAILABLE +from tests.async_mock import patch from tests.common import MockConfigEntry from tests.components.brother import init_integration diff --git a/tests/components/brother/test_sensor.py b/tests/components/brother/test_sensor.py index e88c22f3f40..8e1d52fd2a8 100644 --- a/tests/components/brother/test_sensor.py +++ b/tests/components/brother/test_sensor.py @@ -2,8 +2,6 @@ from datetime import timedelta import json -from asynctest import patch - from homeassistant.components.brother.const import UNIT_PAGES from homeassistant.const import ( ATTR_ENTITY_ID, @@ -16,6 +14,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow +from tests.async_mock import patch from tests.common import async_fire_time_changed, load_fixture from tests.components.brother import init_integration diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index fa6f331363f..8e88c6e564e 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -2,7 +2,6 @@ import datetime from unittest.mock import MagicMock, Mock -from asynctest import patch from caldav.objects import Event import pytest @@ -10,6 +9,8 @@ from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import async_setup_component from homeassistant.util import dt +from tests.async_mock import patch + # pylint: disable=redefined-outer-name DEVICE_DATA = {"name": "Private Calendar", "device_id": "Private Calendar"} diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index e8ab05abb90..401350ec93c 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -4,7 +4,6 @@ import base64 import io from unittest.mock import PropertyMock, mock_open -from asynctest import patch import pytest from homeassistant.components import camera @@ -15,6 +14,7 @@ from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.components.camera import common diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index e2c1064218d..2adb8b63052 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -3,7 +3,6 @@ from typing import Optional from uuid import UUID -from asynctest import MagicMock, Mock, patch import attr import pytest @@ -14,6 +13,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/cert_expiry/test_config_flow.py b/tests/components/cert_expiry/test_config_flow.py index 1b2cc175dcb..9618525ef32 100644 --- a/tests/components/cert_expiry/test_config_flow.py +++ b/tests/components/cert_expiry/test_config_flow.py @@ -2,14 +2,13 @@ import socket import ssl -from asynctest import patch - from homeassistant import data_entry_flow from homeassistant.components.cert_expiry.const import DEFAULT_PORT, DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from .const import HOST, PORT +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/cert_expiry/test_init.py b/tests/components/cert_expiry/test_init.py index d4419b48370..3a2aeb84734 100644 --- a/tests/components/cert_expiry/test_init.py +++ b/tests/components/cert_expiry/test_init.py @@ -1,8 +1,6 @@ """Tests for Cert Expiry setup.""" from datetime import timedelta -from asynctest import patch - from homeassistant.components.cert_expiry.const import DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED @@ -12,6 +10,7 @@ import homeassistant.util.dt as dt_util from .const import HOST, PORT +from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed diff --git a/tests/components/cert_expiry/test_sensors.py b/tests/components/cert_expiry/test_sensors.py index 6594b0988e7..9fcd1ac3efe 100644 --- a/tests/components/cert_expiry/test_sensors.py +++ b/tests/components/cert_expiry/test_sensors.py @@ -3,13 +3,12 @@ from datetime import timedelta import socket import ssl -from asynctest import patch - from homeassistant.const import CONF_HOST, CONF_PORT, STATE_UNAVAILABLE import homeassistant.util.dt as dt_util from .const import HOST, PORT +from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed diff --git a/tests/components/cloud/test_account_link.py b/tests/components/cloud/test_account_link.py index a5f3bef7353..ae34318c452 100644 --- a/tests/components/cloud/test_account_link.py +++ b/tests/components/cloud/test_account_link.py @@ -3,7 +3,6 @@ import asyncio import logging from time import time -from asynctest import CoroutineMock, Mock, patch import pytest from homeassistant import config_entries, data_entry_flow @@ -11,6 +10,7 @@ from homeassistant.components.cloud import account_link from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.util.dt import utcnow +from tests.async_mock import AsyncMock, Mock, patch from tests.common import async_fire_time_changed, mock_platform TEST_DOMAIN = "oauth2_test" @@ -109,7 +109,7 @@ async def test_implementation(hass, flow_handler): flow_finished = asyncio.Future() helper = Mock( - async_get_authorize_url=CoroutineMock(return_value="http://example.com/auth"), + async_get_authorize_url=AsyncMock(return_value="http://example.com/auth"), async_get_tokens=Mock(return_value=flow_finished), ) diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index f65b810d690..c239636e1d3 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -1,12 +1,12 @@ """Test Alexa config.""" import contextlib -from unittest.mock import Mock, patch from homeassistant.components.cloud import ALEXA_SCHEMA, alexa_config from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.util.dt import utcnow -from tests.common import async_fire_time_changed, mock_coro +from tests.async_mock import AsyncMock, Mock, patch +from tests.common import async_fire_time_changed async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs): @@ -28,7 +28,7 @@ async def test_alexa_config_report_state(hass, cloud_prefs): assert conf.should_report_state is False assert conf.is_reporting_states is False - with patch.object(conf, "async_get_access_token", return_value=mock_coro("hello")): + with patch.object(conf, "async_get_access_token", AsyncMock(return_value="hello")): await cloud_prefs.async_update(alexa_report_state=True) await hass.async_block_till_done() @@ -60,7 +60,7 @@ async def test_alexa_config_invalidate_token(hass, cloud_prefs, aioclient_mock): cloud_prefs, Mock( alexa_access_token_url="http://example/alexa_token", - auth=Mock(async_check_token=Mock(side_effect=mock_coro)), + auth=Mock(async_check_token=AsyncMock()), websession=hass.helpers.aiohttp_client.async_get_clientsession(), ), ) @@ -189,13 +189,9 @@ async def test_alexa_update_report_state(hass, cloud_prefs): alexa_config.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None) with patch( - "homeassistant.components.cloud.alexa_config.AlexaConfig." - "async_sync_entities", - side_effect=mock_coro, + "homeassistant.components.cloud.alexa_config.AlexaConfig.async_sync_entities", ) as mock_sync, patch( - "homeassistant.components.cloud.alexa_config." - "AlexaConfig.async_enable_proactive_mode", - side_effect=mock_coro, + "homeassistant.components.cloud.alexa_config.AlexaConfig.async_enable_proactive_mode", ): await cloud_prefs.async_update(alexa_report_state=True) await hass.async_block_till_done() diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py index c4ad22abb2f..38c3067873f 100644 --- a/tests/components/cloud/test_binary_sensor.py +++ b/tests/components/cloud/test_binary_sensor.py @@ -1,11 +1,11 @@ """Tests for the cloud binary sensor.""" from unittest.mock import Mock -from asynctest import patch - from homeassistant.components.cloud.const import DISPATCHER_REMOTE_UPDATE from homeassistant.setup import async_setup_component +from tests.async_mock import patch + async def test_remote_connection_sensor(hass): """Test the remote connection sensor.""" diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index b9e6524b62e..0ce79daab15 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -12,6 +12,7 @@ from homeassistant.setup import async_setup_component from . import mock_cloud, mock_cloud_prefs +from tests.async_mock import AsyncMock from tests.common import mock_coro from tests.components.alexa import test_smart_home as test_alexa @@ -221,7 +222,7 @@ async def test_set_username(hass): prefs = MagicMock( alexa_enabled=False, google_enabled=False, - async_set_username=MagicMock(return_value=mock_coro()), + async_set_username=AsyncMock(return_value=None), ) client = CloudClient(hass, prefs, None, {}, {}) client.cloud = MagicMock(is_logged_in=True, username="mock-username") diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index b08b950a590..9866270473d 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,8 +1,6 @@ """Test the Cloud Google Config.""" from unittest.mock import Mock -from asynctest import patch - from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig from homeassistant.components.google_assistant import helpers as ga_helpers @@ -11,7 +9,8 @@ from homeassistant.core import CoreState from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.util.dt import utcnow -from tests.common import async_fire_time_changed, mock_coro +from tests.async_mock import AsyncMock, patch +from tests.common import async_fire_time_changed async def test_google_update_report_state(hass, cloud_prefs): @@ -43,12 +42,12 @@ async def test_sync_entities(aioclient_mock, hass, cloud_prefs): GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, - Mock(auth=Mock(async_check_token=Mock(side_effect=mock_coro))), + Mock(auth=Mock(async_check_token=AsyncMock())), ) with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", - return_value=mock_coro(Mock(status=HTTP_NOT_FOUND)), + return_value=Mock(status=HTTP_NOT_FOUND), ) as mock_request_sync: assert await config.async_sync_entities("user") == HTTP_NOT_FOUND assert len(mock_request_sync.mock_calls) == 1 diff --git a/tests/components/cloud/test_http_api.py b/tests/components/cloud/test_http_api.py index 2cfca8e6b92..df506d2d8fc 100644 --- a/tests/components/cloud/test_http_api.py +++ b/tests/components/cloud/test_http_api.py @@ -1,9 +1,7 @@ """Tests for the HTTP API for the cloud component.""" import asyncio from ipaddress import ip_network -from unittest.mock import MagicMock, Mock -from asynctest import patch from hass_nabucasa import thingtalk from hass_nabucasa.auth import Unauthenticated, UnknownError from hass_nabucasa.const import STATE_CONNECTED @@ -20,7 +18,7 @@ from homeassistant.core import State from . import mock_cloud, mock_cloud_prefs -from tests.common import mock_coro +from tests.async_mock import AsyncMock, MagicMock, Mock, patch from tests.components.google_assistant import MockConfig SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info" @@ -29,9 +27,7 @@ SUBSCRIPTION_INFO_URL = "https://api-test.hass.io/subscription_info" @pytest.fixture() def mock_auth(): """Mock check token.""" - with patch( - "hass_nabucasa.auth.CognitoAuth.async_check_token", side_effect=mock_coro - ): + with patch("hass_nabucasa.auth.CognitoAuth.async_check_token"): yield @@ -89,7 +85,7 @@ async def test_google_actions_sync(mock_cognito, mock_cloud_login, cloud_client) """Test syncing Google Actions.""" with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", - return_value=mock_coro(Mock(status=200)), + return_value=Mock(status=200), ) as mock_request_sync: req = await cloud_client.post("/api/cloud/google_actions/sync") assert req.status == 200 @@ -100,7 +96,7 @@ async def test_google_actions_sync_fails(mock_cognito, mock_cloud_login, cloud_c """Test syncing Google Actions gone bad.""" with patch( "hass_nabucasa.cloud_api.async_google_actions_request_sync", - return_value=mock_coro(Mock(status=HTTP_INTERNAL_SERVER_ERROR)), + return_value=Mock(status=HTTP_INTERNAL_SERVER_ERROR), ) as mock_request_sync: req = await cloud_client.post("/api/cloud/google_actions/sync") assert req.status == HTTP_INTERNAL_SERVER_ERROR @@ -109,7 +105,7 @@ async def test_google_actions_sync_fails(mock_cognito, mock_cloud_login, cloud_c async def test_login_view(hass, cloud_client): """Test logging in.""" - hass.data["cloud"] = MagicMock(login=MagicMock(return_value=mock_coro())) + hass.data["cloud"] = MagicMock(login=AsyncMock()) req = await cloud_client.post( "/api/cloud/login", json={"email": "my_username", "password": "my_password"} @@ -184,7 +180,7 @@ async def test_login_view_unknown_error(cloud_client): async def test_logout_view(hass, cloud_client): """Test logging out.""" cloud = hass.data["cloud"] = MagicMock() - cloud.logout.return_value = mock_coro() + cloud.logout = AsyncMock(return_value=None) req = await cloud_client.post("/api/cloud/logout") assert req.status == 200 data = await req.json() @@ -450,8 +446,7 @@ async def test_websocket_subscription_not_logged_in(hass, hass_ws_client): """Test querying the status.""" client = await hass_ws_client(hass) with patch( - "hass_nabucasa.Cloud.fetch_subscription_info", - return_value=mock_coro({"return": "value"}), + "hass_nabucasa.Cloud.fetch_subscription_info", return_value={"return": "value"}, ): await client.send_json({"id": 5, "type": "cloud/subscription"}) response = await client.receive_json() @@ -529,7 +524,7 @@ async def test_enabling_webhook(hass, hass_ws_client, setup_api, mock_cloud_logi """Test we call right code to enable webhooks.""" client = await hass_ws_client(hass) with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_create", return_value=mock_coro() + "hass_nabucasa.cloudhooks.Cloudhooks.async_create", return_value={} ) as mock_enable: await client.send_json( {"id": 5, "type": "cloud/cloudhook/create", "webhook_id": "mock-webhook-id"} @@ -544,9 +539,7 @@ async def test_enabling_webhook(hass, hass_ws_client, setup_api, mock_cloud_logi async def test_disabling_webhook(hass, hass_ws_client, setup_api, mock_cloud_login): """Test we call right code to disable webhooks.""" client = await hass_ws_client(hass) - with patch( - "hass_nabucasa.cloudhooks.Cloudhooks.async_delete", return_value=mock_coro() - ) as mock_disable: + with patch("hass_nabucasa.cloudhooks.Cloudhooks.async_delete") as mock_disable: await client.send_json( {"id": 5, "type": "cloud/cloudhook/delete", "webhook_id": "mock-webhook-id"} ) @@ -562,9 +555,7 @@ async def test_enabling_remote(hass, hass_ws_client, setup_api, mock_cloud_login client = await hass_ws_client(hass) cloud = hass.data[DOMAIN] - with patch( - "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro() - ) as mock_connect: + with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect: await client.send_json({"id": 5, "type": "cloud/remote/connect"}) response = await client.receive_json() assert response["success"] @@ -578,9 +569,7 @@ async def test_disabling_remote(hass, hass_ws_client, setup_api, mock_cloud_logi client = await hass_ws_client(hass) cloud = hass.data[DOMAIN] - with patch( - "hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro() - ) as mock_disconnect: + with patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect: await client.send_json({"id": 5, "type": "cloud/remote/disconnect"}) response = await client.receive_json() assert response["success"] @@ -670,9 +659,7 @@ async def test_enabling_remote_trusted_networks_other( client = await hass_ws_client(hass) cloud = hass.data[DOMAIN] - with patch( - "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro() - ) as mock_connect: + with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect: await client.send_json({"id": 5, "type": "cloud/remote/connect"}) response = await client.receive_json() @@ -885,7 +872,7 @@ async def test_thingtalk_convert(hass, hass_ws_client, setup_api): with patch( "homeassistant.components.cloud.http_api.thingtalk.async_convert", - return_value=mock_coro({"hello": "world"}), + return_value={"hello": "world"}, ): await client.send_json( {"id": 5, "type": "cloud/thingtalk/convert", "query": "some-data"} diff --git a/tests/components/config/test_automation.py b/tests/components/config/test_automation.py index 45ffa1d08ec..1d160870169 100644 --- a/tests/components/config/test_automation.py +++ b/tests/components/config/test_automation.py @@ -1,11 +1,11 @@ """Test Automation config panel.""" import json -from asynctest import patch - from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from tests.async_mock import patch + async def test_get_device_config(hass, hass_client): """Test getting device config.""" diff --git a/tests/components/config/test_core.py b/tests/components/config/test_core.py index a722333c037..7c87d8da689 100644 --- a/tests/components/config/test_core.py +++ b/tests/components/config/test_core.py @@ -1,5 +1,4 @@ """Test hassbian config.""" -from asynctest import patch import pytest from homeassistant.bootstrap import async_setup_component @@ -8,6 +7,8 @@ from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_IMPERIAL from homeassistant.util import dt as dt_util, location +from tests.async_mock import patch + ORIG_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/components/config/test_customize.py b/tests/components/config/test_customize.py index d8c9ea19b70..30a475dab77 100644 --- a/tests/components/config/test_customize.py +++ b/tests/components/config/test_customize.py @@ -1,12 +1,12 @@ """Test Customize config panel.""" import json -from asynctest import patch - from homeassistant.bootstrap import async_setup_component from homeassistant.components import config from homeassistant.config import DATA_CUSTOMIZE +from tests.async_mock import patch + async def test_get_entity(hass, hass_client): """Test getting entity.""" diff --git a/tests/components/config/test_group.py b/tests/components/config/test_group.py index d00e0317e9e..f555660fb7a 100644 --- a/tests/components/config/test_group.py +++ b/tests/components/config/test_group.py @@ -2,11 +2,11 @@ import json from unittest.mock import patch -from asynctest import CoroutineMock - from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from tests.async_mock import AsyncMock + VIEW_NAME = "api:config:group:config" @@ -52,7 +52,7 @@ async def test_update_device_config(hass, hass_client): """Mock writing data.""" written.append(data) - mock_call = CoroutineMock() + mock_call = AsyncMock() with patch("homeassistant.components.config._read", mock_read), patch( "homeassistant.components.config._write", mock_write diff --git a/tests/components/config/test_init.py b/tests/components/config/test_init.py index 7f9b62d71f6..6dd16fef7ec 100644 --- a/tests/components/config/test_init.py +++ b/tests/components/config/test_init.py @@ -1,11 +1,11 @@ """Test config init.""" -from unittest.mock import patch from homeassistant.components import config from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.setup import ATTR_COMPONENT, async_setup_component -from tests.common import mock_component, mock_coro +from tests.async_mock import patch +from tests.common import mock_component async def test_config_setup(hass, loop): @@ -20,8 +20,9 @@ async def test_load_on_demand_already_loaded(hass, aiohttp_client): with patch.object(config, "SECTIONS", []), patch.object( config, "ON_DEMAND", ["zwave"] - ), patch("homeassistant.components.config.zwave.async_setup") as stp: - stp.return_value = mock_coro(True) + ), patch( + "homeassistant.components.config.zwave.async_setup", return_value=True + ) as stp: await async_setup_component(hass, "config", {}) @@ -38,8 +39,9 @@ async def test_load_on_demand_on_load(hass, aiohttp_client): assert "config.zwave" not in hass.config.components - with patch("homeassistant.components.config.zwave.async_setup") as stp: - stp.return_value = mock_coro(True) + with patch( + "homeassistant.components.config.zwave.async_setup", return_value=True + ) as stp: hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: "zwave"}) await hass.async_block_till_done() diff --git a/tests/components/config/test_scene.py b/tests/components/config/test_scene.py index b51628f87ae..dcaa950f342 100644 --- a/tests/components/config/test_scene.py +++ b/tests/components/config/test_scene.py @@ -1,12 +1,12 @@ """Test Automation config panel.""" import json -from asynctest import patch - from homeassistant.bootstrap import async_setup_component from homeassistant.components import config from homeassistant.util.yaml import dump +from tests.async_mock import patch + async def test_update_scene(hass, hass_client): """Test updating a scene.""" diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 1670e0c2485..96ab3bca543 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -1,7 +1,8 @@ """Fixtures for component testing.""" -from asynctest import patch import pytest +from tests.async_mock import patch + @pytest.fixture(autouse=True) def prevent_io(): diff --git a/tests/components/coolmaster/test_config_flow.py b/tests/components/coolmaster/test_config_flow.py index 81219c41ff8..49058fc183e 100644 --- a/tests/components/coolmaster/test_config_flow.py +++ b/tests/components/coolmaster/test_config_flow.py @@ -1,9 +1,9 @@ """Test the Coolmaster config flow.""" -from asynctest import patch - from homeassistant import config_entries, setup from homeassistant.components.coolmaster.const import AVAILABLE_MODES, DOMAIN +from tests.async_mock import patch + def _flow_data(): options = {"host": "1.1.1.1"} diff --git a/tests/components/coronavirus/conftest.py b/tests/components/coronavirus/conftest.py index 45d2a00e69d..6e49d2aa164 100644 --- a/tests/components/coronavirus/conftest.py +++ b/tests/components/coronavirus/conftest.py @@ -1,8 +1,9 @@ """Test helpers.""" -from asynctest import Mock, patch import pytest +from tests.async_mock import Mock, patch + @pytest.fixture(autouse=True) def mock_cases(): diff --git a/tests/components/deconz/test_climate.py b/tests/components/deconz/test_climate.py index c03dc72019e..ddc89295cba 100644 --- a/tests/components/deconz/test_climate.py +++ b/tests/components/deconz/test_climate.py @@ -1,14 +1,14 @@ """deCONZ climate platform tests.""" from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz import homeassistant.components.climate as climate from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + SENSORS = { "1": { "id": "Thermostat id", diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 0eda8eb71ab..4f45e36c5b1 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -1,14 +1,14 @@ """deCONZ cover platform tests.""" from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz import homeassistant.components.cover as cover from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + COVERS = { "1": { "id": "Level controllable cover id", diff --git a/tests/components/deconz/test_deconz_event.py b/tests/components/deconz/test_deconz_event.py index dd3289dea23..fc8d4f9d1ba 100644 --- a/tests/components/deconz/test_deconz_event.py +++ b/tests/components/deconz/test_deconz_event.py @@ -1,12 +1,12 @@ """Test deCONZ remote events.""" from copy import deepcopy -from asynctest import Mock - from homeassistant.components.deconz.deconz_event import CONF_DECONZ_EVENT from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.common import async_capture_events + SENSORS = { "1": { "id": "Switch 1 id", @@ -67,53 +67,40 @@ async def test_deconz_events(hass): switch_2_battery_level = hass.states.get("sensor.switch_2_battery_level") assert switch_2_battery_level.state == "100" - mock_listener = Mock() - unsub = hass.bus.async_listen(CONF_DECONZ_EVENT, mock_listener) + events = async_capture_events(hass, CONF_DECONZ_EVENT) gateway.api.sensors["1"].update({"state": {"buttonevent": 2000}}) await hass.async_block_till_done() - assert len(mock_listener.mock_calls) == 1 - assert mock_listener.mock_calls[0][1][0].data == { + assert len(events) == 1 + assert events[0].data == { "id": "switch_1", "unique_id": "00:00:00:00:00:00:00:01", "event": 2000, } - unsub() - - mock_listener = Mock() - unsub = hass.bus.async_listen(CONF_DECONZ_EVENT, mock_listener) - gateway.api.sensors["3"].update({"state": {"buttonevent": 2000}}) await hass.async_block_till_done() - assert len(mock_listener.mock_calls) == 1 - assert mock_listener.mock_calls[0][1][0].data == { + assert len(events) == 2 + assert events[1].data == { "id": "switch_3", "unique_id": "00:00:00:00:00:00:00:03", "event": 2000, "gesture": 1, } - unsub() - - mock_listener = Mock() - unsub = hass.bus.async_listen(CONF_DECONZ_EVENT, mock_listener) - gateway.api.sensors["4"].update({"state": {"gesture": 0}}) await hass.async_block_till_done() - assert len(mock_listener.mock_calls) == 1 - assert mock_listener.mock_calls[0][1][0].data == { + assert len(events) == 3 + assert events[2].data == { "id": "switch_4", "unique_id": "00:00:00:00:00:00:00:04", "event": 1000, "gesture": 0, } - unsub() - await gateway.async_reset() assert len(hass.states.async_all()) == 0 diff --git a/tests/components/deconz/test_gateway.py b/tests/components/deconz/test_gateway.py index e8c9da42ada..9af1a3151e8 100644 --- a/tests/components/deconz/test_gateway.py +++ b/tests/components/deconz/test_gateway.py @@ -1,7 +1,6 @@ """Test deCONZ gateway.""" from copy import deepcopy -from asynctest import Mock, patch import pydeconz import pytest @@ -9,6 +8,7 @@ from homeassistant import config_entries from homeassistant.components import deconz, ssdp from homeassistant.helpers.dispatcher import async_dispatcher_connect +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry API_KEY = "1234567890ABCDEF" diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index 3e2760bc632..8d9d387c91c 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -2,12 +2,12 @@ import asyncio from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + ENTRY1_HOST = "1.2.3.4" ENTRY1_PORT = 80 ENTRY1_API_KEY = "1234567890ABCDEF" diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index e39722fdacb..229c085916e 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,14 +1,14 @@ """deCONZ light platform tests.""" from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz import homeassistant.components.light as light from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + GROUPS = { "1": { "id": "Light group id", diff --git a/tests/components/deconz/test_scene.py b/tests/components/deconz/test_scene.py index 3593fa32355..538c849e831 100644 --- a/tests/components/deconz/test_scene.py +++ b/tests/components/deconz/test_scene.py @@ -1,14 +1,14 @@ """deCONZ scene platform tests.""" from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz import homeassistant.components.scene as scene from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + GROUPS = { "1": { "id": "Light group id", diff --git a/tests/components/deconz/test_services.py b/tests/components/deconz/test_services.py index 07985e4d9f4..e880ea1000b 100644 --- a/tests/components/deconz/test_services.py +++ b/tests/components/deconz/test_services.py @@ -1,6 +1,5 @@ """deCONZ service tests.""" -from asynctest import Mock, patch import pytest import voluptuous as vol @@ -9,6 +8,8 @@ from homeassistant.components.deconz.const import CONF_BRIDGE_ID from .test_gateway import BRIDGEID, setup_deconz_integration +from tests.async_mock import Mock, patch + GROUP = { "1": { "id": "Group 1 id", diff --git a/tests/components/deconz/test_switch.py b/tests/components/deconz/test_switch.py index 6e151ebd47a..b441868859b 100644 --- a/tests/components/deconz/test_switch.py +++ b/tests/components/deconz/test_switch.py @@ -1,14 +1,14 @@ """deCONZ switch platform tests.""" from copy import deepcopy -from asynctest import patch - from homeassistant.components import deconz import homeassistant.components.switch as switch from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration +from tests.async_mock import patch + SWITCHES = { "1": { "id": "On off switch id", diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index 26d0d2165e7..e663600f84f 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -1,5 +1,4 @@ """The tests for the Demo Media player platform.""" -from asynctest import patch import pytest import voluptuous as vol @@ -7,6 +6,7 @@ import homeassistant.components.media_player as mp from homeassistant.helpers.aiohttp_client import DATA_CLIENTSESSION from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.components.media_player import common TEST_ENTITY_ID = "media_player.walkman" diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py index 1b51d93ae6c..23e400adac4 100644 --- a/tests/components/device_sun_light_trigger/test_init.py +++ b/tests/components/device_sun_light_trigger/test_init.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from datetime import datetime -from asynctest import patch import pytest from homeassistant.components import ( @@ -23,6 +22,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 6f384faa15c..1a366b0d2df 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -4,7 +4,6 @@ import json import logging import os -from asynctest import Mock, call, patch import pytest from homeassistant.components import zone @@ -28,6 +27,7 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, call, patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/directv/test_config_flow.py b/tests/components/directv/test_config_flow.py index c5cfec50637..78eddf57c30 100644 --- a/tests/components/directv/test_config_flow.py +++ b/tests/components/directv/test_config_flow.py @@ -1,6 +1,5 @@ """Test the DirecTV config flow.""" from aiohttp import ClientError as HTTPClientError -from asynctest import patch from homeassistant.components.directv.const import CONF_RECEIVER_ID, DOMAIN from homeassistant.components.ssdp import ATTR_UPNP_SERIAL @@ -13,6 +12,7 @@ from homeassistant.data_entry_flow import ( ) from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import patch from tests.components.directv import ( HOST, MOCK_SSDP_DISCOVERY_INFO, diff --git a/tests/components/directv/test_media_player.py b/tests/components/directv/test_media_player.py index 8b428c1b708..7203325fbbe 100644 --- a/tests/components/directv/test_media_player.py +++ b/tests/components/directv/test_media_player.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from typing import Optional -from asynctest import patch from pytest import fixture from homeassistant.components.directv.media_player import ( @@ -55,6 +54,7 @@ from homeassistant.const import ( from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.components.directv import setup_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/directv/test_remote.py b/tests/components/directv/test_remote.py index f93d839b78c..b00f62c0e0c 100644 --- a/tests/components/directv/test_remote.py +++ b/tests/components/directv/test_remote.py @@ -1,6 +1,4 @@ """The tests for the DirecTV remote platform.""" -from asynctest import patch - from homeassistant.components.remote import ( ATTR_COMMAND, DOMAIN as REMOTE_DOMAIN, @@ -9,6 +7,7 @@ from homeassistant.components.remote import ( from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import patch from tests.components.directv import setup_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/discovery/test_init.py b/tests/components/discovery/test_init.py index 86865209de3..9490707e0f6 100644 --- a/tests/components/discovery/test_init.py +++ b/tests/components/discovery/test_init.py @@ -1,7 +1,6 @@ """The tests for the discovery component.""" from unittest.mock import MagicMock -from asynctest import patch import pytest from homeassistant import config_entries @@ -9,6 +8,7 @@ from homeassistant.bootstrap import async_setup_component from homeassistant.components import discovery from homeassistant.util.dt import utcnow +from tests.async_mock import patch from tests.common import async_fire_time_changed, mock_coro # One might consider to "mock" services, but it's easy enough to just use diff --git a/tests/components/doorbird/test_config_flow.py b/tests/components/doorbird/test_config_flow.py index 8b49f87bd0b..7c7c034c6b4 100644 --- a/tests/components/doorbird/test_config_flow.py +++ b/tests/components/doorbird/test_config_flow.py @@ -1,13 +1,12 @@ """Test the DoorBird config flow.""" import urllib -from asynctest import MagicMock, patch - from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.doorbird import CONF_CUSTOM_URL, CONF_TOKEN from homeassistant.components.doorbird.const import CONF_EVENTS, DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry, init_recorder_component VALID_CONFIG = { diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 67c6d3bc58d..936a1e68037 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -11,13 +11,13 @@ from decimal import Decimal from itertools import chain, repeat from unittest.mock import DEFAULT, Mock -import asynctest import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components.dsmr.sensor import DerivativeDSMREntity from homeassistant.const import ENERGY_KILO_WATT_HOUR, TIME_HOURS, VOLUME_CUBIC_METERS +import tests.async_mock from tests.common import assert_setup_component @@ -26,8 +26,8 @@ def mock_connection_factory(monkeypatch): """Mock the create functions for serial and TCP Asyncio connections.""" from dsmr_parser.clients.protocol import DSMRProtocol - transport = asynctest.Mock(spec=asyncio.Transport) - protocol = asynctest.Mock(spec=DSMRProtocol) + transport = tests.async_mock.Mock(spec=asyncio.Transport) + protocol = tests.async_mock.Mock(spec=DSMRProtocol) async def connection_factory(*args, **kwargs): """Return mocked out Asyncio classes.""" @@ -327,7 +327,7 @@ async def test_connection_errors_retry(hass, monkeypatch, mock_connection_factor config = {"platform": "dsmr", "reconnect_interval": 0} # override the mock to have it fail the first time and succeed after - first_fail_connection_factory = asynctest.CoroutineMock( + first_fail_connection_factory = tests.async_mock.AsyncMock( return_value=(transport, protocol), side_effect=chain([TimeoutError], repeat(DEFAULT)), ) diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index b90e6120444..f72e3f481b6 100644 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -1,9 +1,8 @@ """Common functions for tests.""" -from asynctest import CoroutineMock, Mock, call, patch - from homeassistant.components import dynalite from homeassistant.helpers import entity_registry +from tests.async_mock import AsyncMock, Mock, call, patch from tests.common import MockConfigEntry ATTR_SERVICE = "service" @@ -38,7 +37,7 @@ async def create_entity_from_device(hass, device): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices" ) as mock_dyn_dev: - mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + mock_dyn_dev().async_setup = AsyncMock(return_value=True) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"] diff --git a/tests/components/dynalite/test_bridge.py b/tests/components/dynalite/test_bridge.py index 0b093fd5c3e..ea73f75a390 100644 --- a/tests/components/dynalite/test_bridge.py +++ b/tests/components/dynalite/test_bridge.py @@ -1,10 +1,9 @@ """Test Dynalite bridge.""" -from asynctest import CoroutineMock, Mock, patch - from homeassistant.components import dynalite from homeassistant.helpers.dispatcher import async_dispatcher_connect +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry @@ -16,7 +15,7 @@ async def test_update_device(hass): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices" ) as mock_dyn_dev: - mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + mock_dyn_dev().async_setup = AsyncMock(return_value=True) assert await hass.config_entries.async_setup(entry.entry_id) # Not waiting so it add the devices before registration update_device_func = mock_dyn_dev.mock_calls[1][2]["update_device_func"] @@ -46,7 +45,7 @@ async def test_add_devices_then_register(hass): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices" ) as mock_dyn_dev: - mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + mock_dyn_dev().async_setup = AsyncMock(return_value=True) assert await hass.config_entries.async_setup(entry.entry_id) # Not waiting so it add the devices before registration new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"] @@ -79,7 +78,7 @@ async def test_register_then_add_devices(hass): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices" ) as mock_dyn_dev: - mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + mock_dyn_dev().async_setup = AsyncMock(return_value=True) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"] diff --git a/tests/components/dynalite/test_config_flow.py b/tests/components/dynalite/test_config_flow.py index 1a1cdc16f49..11b4d6b524c 100644 --- a/tests/components/dynalite/test_config_flow.py +++ b/tests/components/dynalite/test_config_flow.py @@ -1,11 +1,11 @@ """Test Dynalite config flow.""" -from asynctest import CoroutineMock, patch import pytest from homeassistant import config_entries from homeassistant.components import dynalite +from tests.async_mock import AsyncMock, patch from tests.common import MockConfigEntry @@ -69,7 +69,7 @@ async def test_existing_update(hass): with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices" ) as mock_dyn_dev: - mock_dyn_dev().async_setup = CoroutineMock(return_value=True) + mock_dyn_dev().async_setup = AsyncMock(return_value=True) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() mock_dyn_dev().configure.assert_called_once() diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 8e2290a9c40..8b1393cb32d 100644 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -1,12 +1,11 @@ """Test Dynalite __init__.""" -from asynctest import call, patch - import homeassistant.components.dynalite.const as dynalite from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM from homeassistant.setup import async_setup_component +from tests.async_mock import call, patch from tests.common import MockConfigEntry diff --git a/tests/components/dyson/test_air_quality.py b/tests/components/dyson/test_air_quality.py index fcd801616c9..ab11a1ad897 100644 --- a/tests/components/dyson/test_air_quality.py +++ b/tests/components/dyson/test_air_quality.py @@ -2,7 +2,6 @@ import json from unittest import mock -import asynctest from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_state_v2 import DysonEnvironmentalSensorV2State @@ -19,6 +18,8 @@ from homeassistant.setup import async_setup_component from .common import load_mock_device +from tests.async_mock import patch + def _get_dyson_purecool_device(): """Return a valid device as provided by the Dyson web services.""" @@ -46,8 +47,8 @@ def _get_config(): } -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -65,8 +66,8 @@ async def test_purecool_aiq_attributes(devices, login, hass): assert attributes[dyson.ATTR_VOC] == 35 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -108,8 +109,8 @@ async def test_purecool_aiq_update_state(devices, login, hass): assert attributes[dyson.ATTR_VOC] == 55 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -124,8 +125,8 @@ async def test_purecool_component_setup_only_once(devices, login, hass): assert len(hass.data[dyson.DYSON_AIQ_DEVICES]) == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -140,8 +141,8 @@ async def test_purecool_aiq_without_discovery(devices, login, hass): assert add_entities_mock.call_count == 0 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) diff --git a/tests/components/dyson/test_climate.py b/tests/components/dyson/test_climate.py index 345eae6f553..af17d1f0ab4 100644 --- a/tests/components/dyson/test_climate.py +++ b/tests/components/dyson/test_climate.py @@ -2,7 +2,6 @@ import unittest from unittest import mock -import asynctest from libpurecool.const import FocusMode, HeatMode, HeatState, HeatTarget from libpurecool.dyson_pure_hotcool_link import DysonPureHotCoolLink from libpurecool.dyson_pure_state import DysonPureHotCoolState @@ -14,6 +13,7 @@ from homeassistant.setup import async_setup_component from .common import load_mock_device +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -344,11 +344,11 @@ class DysonTest(unittest.TestCase): assert entity.target_temperature == 23 -@asynctest.patch( +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_device_heat_on(), _get_device_cool()], ) -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) async def test_setup_component_with_parent_discovery( mocked_login, mocked_devices, hass ): diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index 7801c897723..d4db6051960 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -3,7 +3,6 @@ import json import unittest from unittest import mock -import asynctest from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink @@ -28,6 +27,7 @@ from homeassistant.setup import async_setup_component from .common import load_mock_device +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -386,8 +386,8 @@ class DysonTest(unittest.TestCase): dyson_device.set_night_mode.assert_called_with(True) -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecoollink_device()], ) @@ -404,8 +404,8 @@ async def test_purecoollink_attributes(devices, login, hass): assert attributes[ATTR_OSCILLATING] is True -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -426,8 +426,8 @@ async def test_purecool_turn_on(devices, login, hass): assert device.turn_on.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -470,8 +470,8 @@ async def test_purecool_set_speed(devices, login, hass): device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_10) -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -492,8 +492,8 @@ async def test_purecool_turn_off(devices, login, hass): assert device.turn_off.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -526,8 +526,8 @@ async def test_purecool_set_dyson_speed(devices, login, hass): device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_2) -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -562,8 +562,8 @@ async def test_purecool_oscillate(devices, login, hass): assert device.disable_oscillation.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -599,8 +599,8 @@ async def test_purecool_set_night_mode(devices, login, hass): assert device.disable_night_mode.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -635,8 +635,8 @@ async def test_purecool_set_auto_mode(devices, login, hass): assert device.disable_auto_mode.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -671,8 +671,8 @@ async def test_purecool_set_angle(devices, login, hass): device.enable_oscillation.assert_called_with(90, 180) -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -707,8 +707,8 @@ async def test_purecool_set_flow_direction_front(devices, login, hass): assert device.disable_frontal_direction.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -743,8 +743,8 @@ async def test_purecool_set_timer(devices, login, hass): assert device.disable_sleep_timer.call_count == 1 -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -804,8 +804,8 @@ async def test_purecool_update_state(devices, login, hass): assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds() -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) @@ -865,8 +865,8 @@ async def test_purecool_update_state_filter_inv(devices, login, hass): assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds() -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) diff --git a/tests/components/dyson/test_sensor.py b/tests/components/dyson/test_sensor.py index 4d3d1c96101..92bd3bba9aa 100644 --- a/tests/components/dyson/test_sensor.py +++ b/tests/components/dyson/test_sensor.py @@ -2,7 +2,6 @@ import unittest from unittest import mock -import asynctest from libpurecool.dyson_pure_cool import DysonPureCool from libpurecool.dyson_pure_cool_link import DysonPureCoolLink @@ -20,6 +19,7 @@ from homeassistant.setup import async_setup_component from .common import load_mock_device +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -258,8 +258,8 @@ class DysonTest(unittest.TestCase): assert sensor.entity_id == "sensor.dyson_1" -@asynctest.patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@asynctest.patch( +@patch("libpurecool.dyson.DysonAccount.login", return_value=True) +@patch( "libpurecool.dyson.DysonAccount.devices", return_value=[_get_dyson_purecool_device()], ) diff --git a/tests/components/ee_brightbox/test_device_tracker.py b/tests/components/ee_brightbox/test_device_tracker.py index f862539f1df..64f24ec289c 100644 --- a/tests/components/ee_brightbox/test_device_tracker.py +++ b/tests/components/ee_brightbox/test_device_tracker.py @@ -1,7 +1,6 @@ """Tests for the EE BrightBox device scanner.""" from datetime import datetime -from asynctest import patch from eebrightbox import EEBrightBoxException import pytest @@ -9,6 +8,8 @@ from homeassistant.components.device_tracker import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_PLATFORM from homeassistant.setup import async_setup_component +from tests.async_mock import patch + def _configure_mock_get_devices(eebrightbox_mock): eebrightbox_instance = eebrightbox_mock.return_value diff --git a/tests/components/elkm1/test_config_flow.py b/tests/components/elkm1/test_config_flow.py index 2f701a2e146..992483529a5 100644 --- a/tests/components/elkm1/test_config_flow.py +++ b/tests/components/elkm1/test_config_flow.py @@ -1,16 +1,16 @@ """Test the Elk-M1 Control config flow.""" -from asynctest import CoroutineMock, MagicMock, PropertyMock, patch - from homeassistant import config_entries, setup from homeassistant.components.elkm1.const import DOMAIN +from tests.async_mock import AsyncMock, MagicMock, PropertyMock, patch + def mock_elk(invalid_auth=None, sync_complete=None): """Mock m1lib Elk.""" mocked_elk = MagicMock() type(mocked_elk).invalid_auth = PropertyMock(return_value=invalid_auth) - type(mocked_elk).sync_complete = CoroutineMock() + type(mocked_elk).sync_complete = AsyncMock() return mocked_elk diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index 4b951f9a369..b55621226fa 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -1,12 +1,12 @@ """Test config flow.""" from collections import namedtuple -from unittest.mock import MagicMock, patch import pytest from homeassistant.components.esphome import DATA_KEY, config_flow -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import AsyncMock, MagicMock, patch +from tests.common import MockConfigEntry MockDeviceInfo = namedtuple("DeviceInfo", ["uses_password", "name"]) @@ -24,8 +24,8 @@ def mock_client(): return mock_client mock_client.side_effect = mock_constructor - mock_client.connect.return_value = mock_coro() - mock_client.disconnect.return_value = mock_coro() + mock_client.connect = AsyncMock() + mock_client.disconnect = AsyncMock() yield mock_client @@ -53,7 +53,7 @@ async def test_user_connection_works(hass, mock_client): result = await flow.async_step_user(user_input=None) assert result["type"] == "form" - mock_client.device_info.return_value = mock_coro(MockDeviceInfo(False, "test")) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test")) result = await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 80}) @@ -119,7 +119,7 @@ async def test_user_with_password(hass, mock_client): flow = _setup_flow_handler(hass) await flow.async_step_user(user_input=None) - mock_client.device_info.return_value = mock_coro(MockDeviceInfo(True, "test")) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(True, "test")) result = await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 6053}) @@ -142,7 +142,7 @@ async def test_user_invalid_password(hass, mock_api_connection_error, mock_clien flow = _setup_flow_handler(hass) await flow.async_step_user(user_input=None) - mock_client.device_info.return_value = mock_coro(MockDeviceInfo(True, "test")) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(True, "test")) await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 6053}) mock_client.connect.side_effect = mock_api_connection_error @@ -163,7 +163,7 @@ async def test_discovery_initiation(hass, mock_client): "properties": {}, } - mock_client.device_info.return_value = mock_coro(MockDeviceInfo(False, "test8266")) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test8266")) result = await flow.async_step_zeroconf(user_input=service_info) assert result["type"] == "form" @@ -245,7 +245,7 @@ async def test_discovery_duplicate_data(hass, mock_client): "properties": {"address": "test8266.local"}, } - mock_client.device_info.return_value = mock_coro(MockDeviceInfo(False, "test8266")) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test8266")) result = await hass.config_entries.flow.async_init( "esphome", data=service_info, context={"source": "zeroconf"} diff --git a/tests/components/flume/test_config_flow.py b/tests/components/flume/test_config_flow.py index 6ce3391c2c6..a408a652f32 100644 --- a/tests/components/flume/test_config_flow.py +++ b/tests/components/flume/test_config_flow.py @@ -1,10 +1,11 @@ """Test the flume config flow.""" -from asynctest import MagicMock, patch import requests.exceptions from homeassistant import config_entries, setup from homeassistant.components.flume.const import DOMAIN +from tests.async_mock import MagicMock, patch + def _get_mocked_flume_device_list(): flume_device_list_mock = MagicMock() diff --git a/tests/components/flunearyou/test_config_flow.py b/tests/components/flunearyou/test_config_flow.py index 21fcb4798db..a3a0d41e885 100644 --- a/tests/components/flunearyou/test_config_flow.py +++ b/tests/components/flunearyou/test_config_flow.py @@ -1,5 +1,4 @@ """Define tests for the flunearyou config flow.""" -from asynctest import patch from pyflunearyou.errors import FluNearYouError from homeassistant import data_entry_flow @@ -7,6 +6,7 @@ from homeassistant.components.flunearyou import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index c0befe8e69c..ed16e94a283 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -1,5 +1,4 @@ """The tests for the Flux switch platform.""" -from asynctest.mock import patch import pytest from homeassistant.components import light, switch @@ -14,6 +13,7 @@ from homeassistant.core import State from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/freebox/test_config_flow.py b/tests/components/freebox/test_config_flow.py index 4d8fadf0654..a4c8a950299 100644 --- a/tests/components/freebox/test_config_flow.py +++ b/tests/components/freebox/test_config_flow.py @@ -4,7 +4,6 @@ from aiofreepybox.exceptions import ( HttpRequestError, InvalidTokenError, ) -from asynctest import CoroutineMock, patch import pytest from homeassistant import data_entry_flow @@ -12,6 +11,7 @@ from homeassistant.components.freebox.const import DOMAIN from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_HOST, CONF_PORT +from tests.async_mock import AsyncMock, patch from tests.common import MockConfigEntry HOST = "myrouter.freeboxos.fr" @@ -22,17 +22,17 @@ PORT = 1234 def mock_controller_connect(): """Mock a successful connection.""" with patch("homeassistant.components.freebox.router.Freepybox") as service_mock: - service_mock.return_value.open = CoroutineMock() - service_mock.return_value.system.get_config = CoroutineMock( + 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 = CoroutineMock() - service_mock.return_value.connection.get_status = CoroutineMock() - service_mock.return_value.close = CoroutineMock() + 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 diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index ef254871830..10f55bd4db3 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -1,7 +1,6 @@ """The tests for Home Assistant frontend.""" import re -from asynctest import patch import pytest from homeassistant.components.frontend import ( @@ -17,6 +16,7 @@ from homeassistant.const import HTTP_NOT_FOUND from homeassistant.loader import async_get_integration from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import async_capture_events CONFIG_THEMES = {DOMAIN: {CONF_THEMES: {"happy": {"primary-color": "red"}}}} diff --git a/tests/components/gdacs/test_config_flow.py b/tests/components/gdacs/test_config_flow.py index c3c5f5609c4..10e4312eb38 100644 --- a/tests/components/gdacs/test_config_flow.py +++ b/tests/components/gdacs/test_config_flow.py @@ -1,7 +1,6 @@ """Define tests for the GDACS config flow.""" from datetime import timedelta -from asynctest import patch import pytest from homeassistant import data_entry_flow @@ -13,6 +12,8 @@ from homeassistant.const import ( CONF_SCAN_INTERVAL, ) +from tests.async_mock import patch + @pytest.fixture(name="gdacs_setup", autouse=True) def gdacs_setup_fixture(): diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index 3b49fe2af71..2162340154f 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -1,8 +1,6 @@ """The tests for the GDACS Feed integration.""" import datetime -from asynctest import patch - from homeassistant.components import gdacs from homeassistant.components.gdacs import DEFAULT_SCAN_INTERVAL, DOMAIN, FEED from homeassistant.components.gdacs.geo_location import ( @@ -35,6 +33,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.gdacs import _generate_mock_feed_entry diff --git a/tests/components/gdacs/test_init.py b/tests/components/gdacs/test_init.py index 40bda2a196b..c0ac83ebcc2 100644 --- a/tests/components/gdacs/test_init.py +++ b/tests/components/gdacs/test_init.py @@ -1,8 +1,8 @@ """Define tests for the GDACS general setup.""" -from asynctest import patch - from homeassistant.components.gdacs import DOMAIN, FEED +from tests.async_mock import patch + async def test_component_unload_config_entry(hass, config_entry): """Test that loading and unloading of a config entry works.""" diff --git a/tests/components/gdacs/test_sensor.py b/tests/components/gdacs/test_sensor.py index 5e8fd5ad30f..aa8c2a43428 100644 --- a/tests/components/gdacs/test_sensor.py +++ b/tests/components/gdacs/test_sensor.py @@ -1,6 +1,4 @@ """The tests for the GDACS Feed integration.""" -from asynctest import patch - from homeassistant.components import gdacs from homeassistant.components.gdacs import DEFAULT_SCAN_INTERVAL from homeassistant.components.gdacs.sensor import ( @@ -20,6 +18,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.gdacs import _generate_mock_feed_entry diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index 264146a6fda..f0b29539ed5 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -1,7 +1,6 @@ """The tests for the generic_thermostat.""" import datetime -from asynctest import mock import pytest import pytz import voluptuous as vol @@ -32,6 +31,7 @@ from homeassistant.core import DOMAIN as HASS_DOMAIN, CoreState, State, callback from homeassistant.setup import async_setup_component from homeassistant.util.unit_system import METRIC_SYSTEM +from tests.async_mock import patch from tests.common import assert_setup_component, mock_restore_cache from tests.components.climate import common @@ -622,7 +622,7 @@ async def test_temp_change_ac_trigger_on_long_enough(hass, setup_comp_4): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, False) @@ -650,7 +650,7 @@ async def test_temp_change_ac_trigger_off_long_enough(hass, setup_comp_4): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, True) @@ -733,7 +733,7 @@ async def test_temp_change_ac_trigger_on_long_enough_2(hass, setup_comp_5): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, False) @@ -761,7 +761,7 @@ async def test_temp_change_ac_trigger_off_long_enough_2(hass, setup_comp_5): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, True) @@ -852,7 +852,7 @@ async def test_temp_change_heater_trigger_on_long_enough(hass, setup_comp_6): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, False) @@ -871,7 +871,7 @@ async def test_temp_change_heater_trigger_off_long_enough(hass, setup_comp_6): fake_changed = datetime.datetime( 1918, 11, 11, 11, 11, 11, tzinfo=datetime.timezone.utc ) - with mock.patch( + with patch( "homeassistant.helpers.condition.dt_util.utcnow", return_value=fake_changed ): calls = _setup_switch(hass, True) diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index 6b7535a8c85..d79fa3cb18d 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -1,6 +1,4 @@ """The tests for the geojson platform.""" -from asynctest.mock import MagicMock, call, patch - from homeassistant.components import geo_location from homeassistant.components.geo_json_events.geo_location import ( ATTR_EXTERNAL_ID, @@ -23,6 +21,7 @@ from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed URL = "http://geo.json.local/geo_json_events.json" diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index 0264baa8e87..89644445ef1 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -1,8 +1,6 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" import datetime -from asynctest import patch - from homeassistant.components import geonetnz_quakes from homeassistant.components.geo_location import ATTR_SOURCE from homeassistant.components.geonetnz_quakes import DEFAULT_SCAN_INTERVAL, DOMAIN, FEED @@ -31,6 +29,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.geonetnz_quakes import _generate_mock_feed_entry diff --git a/tests/components/geonetnz_quakes/test_init.py b/tests/components/geonetnz_quakes/test_init.py index 85724879f7b..87f2f2a7947 100644 --- a/tests/components/geonetnz_quakes/test_init.py +++ b/tests/components/geonetnz_quakes/test_init.py @@ -1,8 +1,8 @@ """Define tests for the GeoNet NZ Quakes general setup.""" -from asynctest import patch - from homeassistant.components.geonetnz_quakes import DOMAIN, FEED +from tests.async_mock import patch + async def test_component_unload_config_entry(hass, config_entry): """Test that loading and unloading of a config entry works.""" diff --git a/tests/components/geonetnz_quakes/test_sensor.py b/tests/components/geonetnz_quakes/test_sensor.py index 7d7f8333bc0..f02a803994e 100644 --- a/tests/components/geonetnz_quakes/test_sensor.py +++ b/tests/components/geonetnz_quakes/test_sensor.py @@ -1,8 +1,6 @@ """The tests for the GeoNet NZ Quakes Feed integration.""" import datetime -from asynctest import patch - from homeassistant.components import geonetnz_quakes from homeassistant.components.geonetnz_quakes import DEFAULT_SCAN_INTERVAL from homeassistant.components.geonetnz_quakes.sensor import ( @@ -22,6 +20,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.geonetnz_quakes import _generate_mock_feed_entry diff --git a/tests/components/geonetnz_volcano/test_init.py b/tests/components/geonetnz_volcano/test_init.py index 3e2566ffb81..4edf8f452fe 100644 --- a/tests/components/geonetnz_volcano/test_init.py +++ b/tests/components/geonetnz_volcano/test_init.py @@ -1,15 +1,15 @@ """Define tests for the GeoNet NZ Volcano general setup.""" -from asynctest import CoroutineMock, patch - from homeassistant.components.geonetnz_volcano import DOMAIN, FEED +from tests.async_mock import AsyncMock, patch + async def test_component_unload_config_entry(hass, config_entry): """Test that loading and unloading of a config entry works.""" config_entry.add_to_hass(hass) with patch( "aio_geojson_geonetnz_volcano.GeonetnzVolcanoFeedManager.update", - new_callable=CoroutineMock, + new_callable=AsyncMock, ) as mock_feed_manager_update: # Load config entry. assert await hass.config_entries.async_setup(config_entry.entry_id) diff --git a/tests/components/geonetnz_volcano/test_sensor.py b/tests/components/geonetnz_volcano/test_sensor.py index 8f71e3c4757..ccf6248bfa9 100644 --- a/tests/components/geonetnz_volcano/test_sensor.py +++ b/tests/components/geonetnz_volcano/test_sensor.py @@ -1,6 +1,4 @@ """The tests for the GeoNet NZ Volcano Feed integration.""" -from asynctest import CoroutineMock, patch - from homeassistant.components import geonetnz_volcano from homeassistant.components.geo_location import ATTR_DISTANCE from homeassistant.components.geonetnz_volcano import DEFAULT_SCAN_INTERVAL @@ -23,6 +21,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM +from tests.async_mock import AsyncMock, patch from tests.common import async_fire_time_changed from tests.components.geonetnz_volcano import _generate_mock_feed_entry @@ -49,7 +48,7 @@ async def test_setup(hass): # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=CoroutineMock + "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock ) as mock_feed_update: mock_feed_update.return_value = "OK", [mock_entry_1, mock_entry_2, mock_entry_3] assert await async_setup_component(hass, geonetnz_volcano.DOMAIN, CONFIG) @@ -139,7 +138,7 @@ async def test_setup_imperial(hass): # Patching 'utcnow' to gain more control over the timed update. utcnow = dt_util.utcnow() with patch("homeassistant.util.dt.utcnow", return_value=utcnow), patch( - "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=CoroutineMock + "aio_geojson_client.feed.GeoJsonFeed.update", new_callable=AsyncMock ) as mock_feed_update, patch( "aio_geojson_client.feed.GeoJsonFeed.__init__" ) as mock_feed_init: diff --git a/tests/components/gios/test_config_flow.py b/tests/components/gios/test_config_flow.py index 3a4aff6d9ad..b2f7ceec9e4 100644 --- a/tests/components/gios/test_config_flow.py +++ b/tests/components/gios/test_config_flow.py @@ -1,5 +1,4 @@ """Define tests for the GIOS config flow.""" -from asynctest import patch from gios import ApiError from homeassistant import data_entry_flow @@ -7,6 +6,8 @@ from homeassistant.components.gios import config_flow from homeassistant.components.gios.const import CONF_STATION_ID from homeassistant.const import CONF_NAME +from tests.async_mock import patch + CONFIG = { CONF_NAME: "Foo", CONF_STATION_ID: 123, diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 79684bdeb44..8f6b0908f83 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -1,8 +1,8 @@ """Tests for the Google Assistant integration.""" -from asynctest.mock import MagicMock - from homeassistant.components.google_assistant import helpers +from tests.async_mock import MagicMock + def mock_google_config_store(agent_user_ids=None): """Fake a storage for google assistant.""" diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 9f6f83fab59..4943212bd5a 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -1,7 +1,6 @@ """Test Google Assistant helpers.""" from datetime import timedelta -from asynctest.mock import Mock, call, patch import pytest from homeassistant.components.google_assistant import helpers @@ -14,6 +13,7 @@ from homeassistant.util import dt from . import MockConfig +from tests.async_mock import Mock, call, patch from tests.common import ( async_capture_events, async_fire_time_changed, diff --git a/tests/components/google_assistant/test_http.py b/tests/components/google_assistant/test_http.py index ff159e4e10c..4b9461e6304 100644 --- a/tests/components/google_assistant/test_http.py +++ b/tests/components/google_assistant/test_http.py @@ -1,8 +1,6 @@ """Test Google http services.""" from datetime import datetime, timedelta, timezone -from asynctest import ANY, patch - from homeassistant.components.google_assistant import GOOGLE_ASSISTANT_SCHEMA from homeassistant.components.google_assistant.const import ( HOMEGRAPH_TOKEN_URL, @@ -14,6 +12,8 @@ from homeassistant.components.google_assistant.http import ( _get_homegraph_token, ) +from tests.async_mock import ANY, patch + DUMMY_CONFIG = GOOGLE_ASSISTANT_SCHEMA( { "project_id": "1234", diff --git a/tests/components/google_assistant/test_report_state.py b/tests/components/google_assistant/test_report_state.py index fd9cad27ffa..7dd3faba7ab 100644 --- a/tests/components/google_assistant/test_report_state.py +++ b/tests/components/google_assistant/test_report_state.py @@ -1,12 +1,11 @@ """Test Google report state.""" -from unittest.mock import patch - from homeassistant.components.google_assistant import error, report_state from homeassistant.util.dt import utcnow from . import BASIC_CONFIG -from tests.common import async_fire_time_changed, mock_coro +from tests.async_mock import AsyncMock, patch +from tests.common import async_fire_time_changed async def test_report_state(hass, caplog): @@ -15,7 +14,7 @@ async def test_report_state(hass, caplog): hass.states.async_set("switch.ac", "on") with patch.object( - BASIC_CONFIG, "async_report_state_all", side_effect=mock_coro + BASIC_CONFIG, "async_report_state_all", AsyncMock() ) as mock_report, patch.object(report_state, "INITIAL_REPORT_DELAY", 0): unsub = report_state.async_enable_report_state(hass, BASIC_CONFIG) @@ -34,7 +33,7 @@ async def test_report_state(hass, caplog): } with patch.object( - BASIC_CONFIG, "async_report_state_all", side_effect=mock_coro + BASIC_CONFIG, "async_report_state_all", AsyncMock() ) as mock_report: hass.states.async_set("light.kitchen", "on") await hass.async_block_till_done() @@ -47,7 +46,7 @@ async def test_report_state(hass, caplog): # Test that state changes that change something that Google doesn't care about # do not trigger a state report. with patch.object( - BASIC_CONFIG, "async_report_state_all", side_effect=mock_coro + BASIC_CONFIG, "async_report_state_all", AsyncMock() ) as mock_report: hass.states.async_set( "light.kitchen", "on", {"irrelevant": "should_be_ignored"} @@ -58,7 +57,7 @@ async def test_report_state(hass, caplog): # Test that entities that we can't query don't report a state with patch.object( - BASIC_CONFIG, "async_report_state_all", side_effect=mock_coro + BASIC_CONFIG, "async_report_state_all", AsyncMock() ) as mock_report, patch( "homeassistant.components.google_assistant.report_state.GoogleEntity.query_serialize", side_effect=error.SmartHomeError("mock-error", "mock-msg"), @@ -72,7 +71,7 @@ async def test_report_state(hass, caplog): unsub() with patch.object( - BASIC_CONFIG, "async_report_state_all", side_effect=mock_coro + BASIC_CONFIG, "async_report_state_all", AsyncMock() ) as mock_report: hass.states.async_set("light.kitchen", "on") await hass.async_block_till_done() diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index d3c9da94f04..c469b8b9efe 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -1,5 +1,4 @@ """Test Google Smart Home.""" -from asynctest import Mock, patch import pytest from homeassistant.components import camera @@ -28,6 +27,7 @@ from homeassistant.setup import async_setup_component from . import BASIC_CONFIG, MockConfig +from tests.async_mock import Mock, patch from tests.common import mock_area_registry, mock_device_registry, mock_registry REQ_ID = "ff36a3cc-ec34-11e6-b1a0-64510650abcf" diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index fed084586cc..f133db26d89 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -2,7 +2,6 @@ import logging from unittest.mock import Mock -from asynctest import patch import pytest from homeassistant.components import ( @@ -46,6 +45,7 @@ from homeassistant.util import color from . import BASIC_CONFIG, MockConfig +from tests.async_mock import patch from tests.common import async_mock_service _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/griddy/test_config_flow.py b/tests/components/griddy/test_config_flow.py index 1ab29aebece..79f99d7f8b1 100644 --- a/tests/components/griddy/test_config_flow.py +++ b/tests/components/griddy/test_config_flow.py @@ -1,11 +1,11 @@ """Test the Griddy Power config flow.""" import asyncio -from asynctest import MagicMock, patch - from homeassistant import config_entries, setup from homeassistant.components.griddy.const import DOMAIN +from tests.async_mock import MagicMock, patch + async def test_form(hass): """Test we get the form.""" diff --git a/tests/components/griddy/test_sensor.py b/tests/components/griddy/test_sensor.py index 995327a9b56..ae3d0c3be84 100644 --- a/tests/components/griddy/test_sensor.py +++ b/tests/components/griddy/test_sensor.py @@ -2,12 +2,12 @@ import json import os -from asynctest 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.async_mock import patch from tests.common import load_fixture diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index 94d78d62877..ed807ed57d9 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -1,8 +1,6 @@ """The tests for the Group Light platform.""" from unittest.mock import MagicMock -import asynctest - from homeassistant.components.group import DOMAIN import homeassistant.components.group.light as group from homeassistant.components.light import ( @@ -32,6 +30,8 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +import tests.async_mock + async def test_default_state(hass): """Test light group default state.""" @@ -559,7 +559,7 @@ async def test_invalid_service_calls(hass): grouped_light = add_entities.call_args[0][0][0] grouped_light.hass = hass - with asynctest.patch.object(hass.services, "async_call") as mock_call: + with tests.async_mock.patch.object(hass.services, "async_call") as mock_call: await grouped_light.async_turn_on(brightness=150, four_oh_four="404") data = {ATTR_ENTITY_ID: ["light.test1", "light.test2"], ATTR_BRIGHTNESS: 150} mock_call.assert_called_once_with( diff --git a/tests/components/harmony/test_config_flow.py b/tests/components/harmony/test_config_flow.py index 30421756d22..079923330e2 100644 --- a/tests/components/harmony/test_config_flow.py +++ b/tests/components/harmony/test_config_flow.py @@ -1,15 +1,15 @@ """Test the Logitech Harmony Hub config flow.""" -from asynctest import CoroutineMock, MagicMock, patch - from homeassistant import config_entries, setup from homeassistant.components.harmony.config_flow import CannotConnect from homeassistant.components.harmony.const import DOMAIN +from tests.async_mock import AsyncMock, MagicMock, patch + def _get_mock_harmonyapi(connect=None, close=None): harmonyapi_mock = MagicMock() - type(harmonyapi_mock).connect = CoroutineMock(return_value=connect) - type(harmonyapi_mock).close = CoroutineMock(return_value=close) + type(harmonyapi_mock).connect = AsyncMock(return_value=connect) + type(harmonyapi_mock).close = AsyncMock(return_value=close) return harmonyapi_mock diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py index 9a50da4ce41..abcf62915b6 100644 --- a/tests/components/hassio/conftest.py +++ b/tests/components/hassio/conftest.py @@ -1,6 +1,5 @@ """Fixtures for Hass.io.""" import os -from unittest.mock import Mock, patch import pytest @@ -10,7 +9,7 @@ from homeassistant.setup import async_setup_component from . import HASSIO_TOKEN -from tests.common import mock_coro +from tests.async_mock import Mock, patch @pytest.fixture @@ -18,7 +17,7 @@ def hassio_env(): """Fixture to inject hassio env.""" with patch.dict(os.environ, {"HASSIO": "127.0.0.1"}), patch( "homeassistant.components.hassio.HassIO.is_connected", - Mock(return_value=mock_coro({"result": "ok", "data": {}})), + return_value={"result": "ok", "data": {}}, ), patch.dict(os.environ, {"HASSIO_TOKEN": "123456"}), patch( "homeassistant.components.hassio.HassIO.get_homeassistant_info", Mock(side_effect=HassioAPIError()), @@ -31,10 +30,10 @@ def hassio_stubs(hassio_env, hass, hass_client, aioclient_mock): """Create mock hassio http client.""" with patch( "homeassistant.components.hassio.HassIO.update_hass_api", - return_value=mock_coro({"result": "ok"}), + return_value={"result": "ok"}, ) as hass_api, patch( "homeassistant.components.hassio.HassIO.update_hass_timezone", - return_value=mock_coro({"result": "ok"}), + return_value={"result": "ok"}, ), patch( "homeassistant.components.hassio.HassIO.get_homeassistant_info", side_effect=HassioAPIError(), diff --git a/tests/components/hassio/test_addon_panel.py b/tests/components/hassio/test_addon_panel.py index d2ad673111d..9b11fd6dfc2 100644 --- a/tests/components/hassio/test_addon_panel.py +++ b/tests/components/hassio/test_addon_panel.py @@ -1,11 +1,9 @@ """Test add-on panel.""" -from unittest.mock import Mock, patch - import pytest from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import patch @pytest.fixture(autouse=True) @@ -49,7 +47,6 @@ async def test_hassio_addon_panel_startup(hass, aioclient_mock, hassio_env): with patch( "homeassistant.components.hassio.addon_panel._register_panel", - Mock(return_value=mock_coro()), ) as mock_panel: await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() @@ -92,7 +89,6 @@ async def test_hassio_addon_panel_api(hass, aioclient_mock, hassio_env, hass_cli with patch( "homeassistant.components.hassio.addon_panel._register_panel", - Mock(return_value=mock_coro()), ) as mock_panel: await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() diff --git a/tests/components/hassio/test_auth.py b/tests/components/hassio/test_auth.py index 621efa1cb9e..e97c5bc66fb 100644 --- a/tests/components/hassio/test_auth.py +++ b/tests/components/hassio/test_auth.py @@ -1,10 +1,9 @@ """The tests for the hassio component.""" -from unittest.mock import Mock, patch from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR from homeassistant.exceptions import HomeAssistantError -from tests.common import mock_coro +from tests.async_mock import Mock, patch async def test_auth_success(hass, hassio_client_supervisor): @@ -12,7 +11,6 @@ async def test_auth_success(hass, hassio_client_supervisor): with patch( "homeassistant.auth.providers.homeassistant." "HassAuthProvider.async_validate_login", - Mock(return_value=mock_coro()), ) as mock_login: resp = await hassio_client_supervisor.post( "/api/hassio_auth", @@ -29,7 +27,6 @@ async def test_auth_fails_no_supervisor(hass, hassio_client): with patch( "homeassistant.auth.providers.homeassistant." "HassAuthProvider.async_validate_login", - Mock(return_value=mock_coro()), ) as mock_login: resp = await hassio_client.post( "/api/hassio_auth", @@ -46,7 +43,6 @@ async def test_auth_fails_no_auth(hass, hassio_noauth_client): with patch( "homeassistant.auth.providers.homeassistant." "HassAuthProvider.async_validate_login", - Mock(return_value=mock_coro()), ) as mock_login: resp = await hassio_noauth_client.post( "/api/hassio_auth", @@ -110,7 +106,6 @@ async def test_login_success_extra(hass, hassio_client_supervisor): with patch( "homeassistant.auth.providers.homeassistant." "HassAuthProvider.async_validate_login", - Mock(return_value=mock_coro()), ) as mock_login: resp = await hassio_client_supervisor.post( "/api/hassio_auth", @@ -131,7 +126,6 @@ async def test_password_success(hass, hassio_client_supervisor): """Test no auth needed for .""" with patch( "homeassistant.components.hassio.auth.HassIOPasswordReset._change_password", - Mock(return_value=mock_coro()), ) as mock_change: resp = await hassio_client_supervisor.post( "/api/hassio_auth/password_reset", @@ -147,7 +141,6 @@ async def test_password_fails_no_supervisor(hass, hassio_client): """Test if only supervisor can access.""" with patch( "homeassistant.auth.providers.homeassistant.Data.async_save", - Mock(return_value=mock_coro()), ) as mock_save: resp = await hassio_client.post( "/api/hassio_auth/password_reset", @@ -163,7 +156,6 @@ async def test_password_fails_no_auth(hass, hassio_noauth_client): """Test if only supervisor can access.""" with patch( "homeassistant.auth.providers.homeassistant.Data.async_save", - Mock(return_value=mock_coro()), ) as mock_save: resp = await hassio_noauth_client.post( "/api/hassio_auth/password_reset", @@ -179,7 +171,6 @@ async def test_password_no_user(hass, hassio_client_supervisor): """Test no auth needed for .""" with patch( "homeassistant.auth.providers.homeassistant.Data.async_save", - Mock(return_value=mock_coro()), ) as mock_save: resp = await hassio_client_supervisor.post( "/api/hassio_auth/password_reset", diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index a0d64440041..fd4fa26f813 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -1,11 +1,9 @@ """Test config flow.""" -from unittest.mock import Mock, patch - from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import Mock, patch async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): @@ -41,7 +39,7 @@ async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): with patch( "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", - Mock(return_value=mock_coro({"type": "abort"})), + return_value={"type": "abort"}, ) as mock_mqtt: hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -91,13 +89,13 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client with patch( "homeassistant.components.hassio.HassIO.update_hass_api", - Mock(return_value=mock_coro({"result": "ok"})), + return_value={"result": "ok"}, ), patch( "homeassistant.components.hassio.HassIO.get_homeassistant_info", Mock(side_effect=HassioAPIError()), ), patch( "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", - Mock(return_value=mock_coro({"type": "abort"})), + return_value={"type": "abort"}, ) as mock_mqtt: await hass.async_start() await async_setup_component(hass, "hassio", {}) @@ -144,7 +142,7 @@ async def test_hassio_discovery_webhook(hass, aioclient_mock, hassio_client): with patch( "homeassistant.components.mqtt.config_flow.FlowHandler.async_step_hassio", - Mock(return_value=mock_coro({"type": "abort"})), + return_value={"type": "abort"}, ) as mock_mqtt: resp = await hassio_client.post( "/api/hassio_push/discovery/testuuid", diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 26caec65b40..13bae001448 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -1,7 +1,6 @@ """The tests for the hassio component.""" import os -from asynctest import patch import pytest from homeassistant.auth.const import GROUP_ID_ADMIN @@ -9,6 +8,8 @@ from homeassistant.components import frontend from homeassistant.components.hassio import STORAGE_KEY from homeassistant.setup import async_setup_component +from tests.async_mock import patch + MOCK_ENVIRON = {"HASSIO": "127.0.0.1", "HASSIO_TOKEN": "abcdefgh"} diff --git a/tests/components/heos/conftest.py b/tests/components/heos/conftest.py index 5201b7f7b8a..86be36e8188 100644 --- a/tests/components/heos/conftest.py +++ b/tests/components/heos/conftest.py @@ -1,7 +1,6 @@ """Configuration for HEOS tests.""" from typing import Dict, Sequence -from asynctest.mock import Mock, patch as patch from pyheos import Dispatcher, Heos, HeosPlayer, HeosSource, InputSource, const import pytest @@ -9,6 +8,7 @@ from homeassistant.components import ssdp from homeassistant.components.heos import DOMAIN from homeassistant.const import CONF_HOST +from tests.async_mock import Mock, patch as patch from tests.common import MockConfigEntry diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py index b83923943bd..d90c4263240 100644 --- a/tests/components/heos/test_config_flow.py +++ b/tests/components/heos/test_config_flow.py @@ -1,7 +1,6 @@ """Tests for the Heos config flow module.""" from urllib.parse import urlparse -from asynctest import patch from pyheos import HeosError from homeassistant import data_entry_flow @@ -10,6 +9,8 @@ from homeassistant.components.heos.config_flow import HeosFlowHandler from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS from homeassistant.const import CONF_HOST +from tests.async_mock import patch + async def test_flow_aborts_already_setup(hass, config_entry): """Test flow aborts when entry already setup.""" diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index cfbdcb9198a..a6852e3db41 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -1,7 +1,6 @@ """Tests for the init module.""" import asyncio -from asynctest import Mock, patch from pyheos import CommandFailedError, HeosError, const import pytest @@ -20,6 +19,8 @@ from homeassistant.const import CONF_HOST from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch + async def test_async_setup_creates_entry(hass, config): """Test component setup creates entry from config.""" diff --git a/tests/components/hisense_aehw4a1/test_init.py b/tests/components/hisense_aehw4a1/test_init.py index f2af78fe160..498e8c4c306 100644 --- a/tests/components/hisense_aehw4a1/test_init.py +++ b/tests/components/hisense_aehw4a1/test_init.py @@ -1,11 +1,12 @@ """Tests for the Hisense AEH-W4A1 init file.""" -from asynctest import patch from pyaehw4a1 import exceptions from homeassistant import config_entries, data_entry_flow from homeassistant.components import hisense_aehw4a1 from homeassistant.setup import async_setup_component +from tests.async_mock import patch + async def test_creating_entry_sets_up_climate_discovery(hass): """Test setting up Hisense AEH-W4A1 loads the climate component.""" diff --git a/tests/components/homeassistant/test_init.py b/tests/components/homeassistant/test_init.py index fddd149942e..b9309d70d63 100644 --- a/tests/components/homeassistant/test_init.py +++ b/tests/components/homeassistant/test_init.py @@ -3,7 +3,6 @@ import asyncio import unittest -from asynctest import Mock, patch import pytest import voluptuous as vol import yaml @@ -33,6 +32,7 @@ from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.helpers import entity from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch from tests.common import ( async_capture_events, async_mock_service, diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index 12d12082a33..258f26e78a6 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -2,7 +2,6 @@ import os from zlib import adler32 -from asynctest import patch import pytest from homeassistant.components.homekit.aidmanager import ( @@ -13,6 +12,7 @@ from homeassistant.components.homekit.aidmanager import ( from homeassistant.helpers import device_registry from homeassistant.helpers.storage import STORAGE_DIR +from tests.async_mock import patch from tests.common import MockConfigEntry, mock_device_registry, mock_registry diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index b63ee6d0bd9..8a1d911ef04 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,7 +1,6 @@ """Tests for the HomeKit component.""" from unittest.mock import ANY, Mock, patch -from asynctest import CoroutineMock import pytest from zeroconf import InterfaceChoice @@ -44,6 +43,7 @@ from homeassistant.core import State from homeassistant.helpers import device_registry from homeassistant.helpers.entityfilter import generate_filter +from tests.async_mock import AsyncMock from tests.common import MockConfigEntry, mock_device_registry, mock_registry from tests.components.homekit.common import patch_debounce @@ -104,7 +104,7 @@ async def test_setup_auto_start_disabled(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() - type(homekit).async_start = CoroutineMock() + type(homekit).async_start = AsyncMock() assert await setup.async_setup_component(hass, DOMAIN, config) mock_homekit.assert_any_call( diff --git a/tests/components/homekit/test_init.py b/tests/components/homekit/test_init.py index e01588305d5..6d01413da8f 100644 --- a/tests/components/homekit/test_init.py +++ b/tests/components/homekit/test_init.py @@ -1,6 +1,4 @@ """Test HomeKit initialization.""" -from asynctest import patch - from homeassistant import core as ha from homeassistant.components import logbook from homeassistant.components.homekit.const import ( @@ -12,6 +10,8 @@ from homeassistant.components.homekit.const import ( from homeassistant.const import ATTR_ENTITY_ID, ATTR_SERVICE from homeassistant.setup import async_setup_component +from tests.async_mock import patch + async def test_humanify_homekit_changed_event(hass, hk_driver): """Test humanifying HomeKit changed event.""" diff --git a/tests/components/homekit_controller/conftest.py b/tests/components/homekit_controller/conftest.py index 99e86335cdb..ac4a0b4b5d6 100644 --- a/tests/components/homekit_controller/conftest.py +++ b/tests/components/homekit_controller/conftest.py @@ -3,9 +3,10 @@ import datetime from unittest import mock from aiohomekit.testing import FakeController -import asynctest import pytest +import tests.async_mock + @pytest.fixture def utcnow(request): @@ -20,5 +21,5 @@ def utcnow(request): def controller(hass): """Replace aiohomekit.Controller with an instance of aiohomekit.testing.FakeController.""" instance = FakeController() - with asynctest.patch("aiohomekit.Controller", return_value=instance): + with tests.async_mock.patch("aiohomekit.Controller", return_value=instance): yield instance diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 302104c0f49..a9aef723164 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -5,12 +5,12 @@ import aiohomekit from aiohomekit.model import Accessories, Accessory from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes -import asynctest -from asynctest import patch import pytest from homeassistant.components.homekit_controller import config_flow +import tests.async_mock +from tests.async_mock import patch from tests.common import MockConfigEntry PAIRING_START_FORM_ERRORS = [ @@ -63,15 +63,15 @@ def _setup_flow_handler(hass, pairing=None): flow.hass = hass flow.context = {} - finish_pairing = asynctest.CoroutineMock(return_value=pairing) + finish_pairing = tests.async_mock.AsyncMock(return_value=pairing) discovery = mock.Mock() discovery.device_id = "00:00:00:00:00:00" - discovery.start_pairing = asynctest.CoroutineMock(return_value=finish_pairing) + discovery.start_pairing = tests.async_mock.AsyncMock(return_value=finish_pairing) flow.controller = mock.Mock() flow.controller.pairings = {} - flow.controller.find_ip_by_device_id = asynctest.CoroutineMock( + flow.controller.find_ip_by_device_id = tests.async_mock.AsyncMock( return_value=discovery ) @@ -368,7 +368,7 @@ async def test_pair_abort_errors_on_finish(hass, controller, exception, expected # User initiates pairing - this triggers the device to show a pairing code # and then HA to show a pairing form - finish_pairing = asynctest.CoroutineMock(side_effect=exception("error")) + finish_pairing = tests.async_mock.AsyncMock(side_effect=exception("error")) with patch.object(device, "start_pairing", return_value=finish_pairing): result = await hass.config_entries.flow.async_configure(result["flow_id"]) @@ -408,7 +408,7 @@ async def test_pair_form_errors_on_finish(hass, controller, exception, expected) # User initiates pairing - this triggers the device to show a pairing code # and then HA to show a pairing form - finish_pairing = asynctest.CoroutineMock(side_effect=exception("error")) + finish_pairing = tests.async_mock.AsyncMock(side_effect=exception("error")) with patch.object(device, "start_pairing", return_value=finish_pairing): result = await hass.config_entries.flow.async_configure(result["flow_id"]) diff --git a/tests/components/homematicip_cloud/conftest.py b/tests/components/homematicip_cloud/conftest.py index b1933604fbe..3ada7e7de0a 100644 --- a/tests/components/homematicip_cloud/conftest.py +++ b/tests/components/homematicip_cloud/conftest.py @@ -1,5 +1,4 @@ """Initializer helpers for HomematicIP fake server.""" -from asynctest import CoroutineMock, MagicMock, Mock, patch from homematicip.aio.auth import AsyncAuth from homematicip.aio.connection import AsyncConnection from homematicip.aio.home import AsyncHome @@ -23,6 +22,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .helper import AUTH_TOKEN, HAPID, HAPPIN, HomeFactory +from tests.async_mock import AsyncMock, MagicMock, Mock, patch from tests.common import MockConfigEntry @@ -37,8 +37,8 @@ def mock_connection_fixture() -> AsyncConnection: connection._restCall.side_effect = ( # pylint: disable=protected-access _rest_call_side_effect ) - connection.api_call = CoroutineMock(return_value=True) - connection.init = CoroutineMock(side_effect=True) + connection.api_call = AsyncMock(return_value=True) + connection.init = AsyncMock(side_effect=True) return connection diff --git a/tests/components/homematicip_cloud/helper.py b/tests/components/homematicip_cloud/helper.py index 403dbd873be..fede095e57d 100644 --- a/tests/components/homematicip_cloud/helper.py +++ b/tests/components/homematicip_cloud/helper.py @@ -1,7 +1,6 @@ """Helper for HomematicIP Cloud Tests.""" import json -from asynctest import Mock, patch from homematicip.aio.class_maps import ( TYPE_CLASS_MAP, TYPE_GROUP_MAP, @@ -22,6 +21,7 @@ from homeassistant.components.homematicip_cloud.hap import HomematicipHAP from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch from tests.common import load_fixture HAPID = "3014F7110000000000000001" diff --git a/tests/components/homematicip_cloud/test_config_flow.py b/tests/components/homematicip_cloud/test_config_flow.py index e6e145fefba..e9ecab2dbfb 100644 --- a/tests/components/homematicip_cloud/test_config_flow.py +++ b/tests/components/homematicip_cloud/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for HomematicIP Cloud config flow.""" -from asynctest import patch - from homeassistant.components.homematicip_cloud.const import ( DOMAIN as HMIPC_DOMAIN, HMIPC_AUTHTOKEN, @@ -9,6 +7,7 @@ from homeassistant.components.homematicip_cloud.const import ( HMIPC_PIN, ) +from tests.async_mock import patch from tests.common import MockConfigEntry DEFAULT_CONFIG = {HMIPC_HAPID: "ABC123", HMIPC_PIN: "123", HMIPC_NAME: "hmip"} diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py index 71efac3a7c9..8a8d52d167a 100644 --- a/tests/components/homematicip_cloud/test_device.py +++ b/tests/components/homematicip_cloud/test_device.py @@ -1,5 +1,4 @@ """Common tests for HomematicIP devices.""" -from asynctest import patch from homematicip.base.enums import EventType from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN @@ -14,6 +13,8 @@ from .helper import ( get_and_check_entity_basics, ) +from tests.async_mock import patch + async def test_hmip_load_all_supported_devices(hass, default_mock_hap_factory): """Ensure that all supported devices could be loaded.""" diff --git a/tests/components/homematicip_cloud/test_hap.py b/tests/components/homematicip_cloud/test_hap.py index e6e143973f3..ca701622e90 100644 --- a/tests/components/homematicip_cloud/test_hap.py +++ b/tests/components/homematicip_cloud/test_hap.py @@ -1,6 +1,5 @@ """Test HomematicIP Cloud accesspoint.""" -from asynctest import Mock, patch from homematicip.aio.auth import AsyncAuth from homematicip.base.base_connection import HmipConnectionError import pytest @@ -22,6 +21,8 @@ from homeassistant.exceptions import ConfigEntryNotReady from .helper import HAPID, HAPPIN +from tests.async_mock import Mock, patch + async def test_auth_setup(hass): """Test auth setup for client registration.""" diff --git a/tests/components/homematicip_cloud/test_init.py b/tests/components/homematicip_cloud/test_init.py index 8f2753bc499..5b201da8aa8 100644 --- a/tests/components/homematicip_cloud/test_init.py +++ b/tests/components/homematicip_cloud/test_init.py @@ -1,6 +1,5 @@ """Test HomematicIP Cloud setup process.""" -from asynctest import CoroutineMock, Mock, patch from homematicip.base.base_connection import HmipConnectionError from homeassistant.components.homematicip_cloud.const import ( @@ -21,6 +20,7 @@ from homeassistant.config_entries import ( from homeassistant.const import CONF_NAME from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry @@ -139,12 +139,12 @@ async def test_unload_entry(hass): with patch("homeassistant.components.homematicip_cloud.HomematicipHAP") as mock_hap: instance = mock_hap.return_value - instance.async_setup = CoroutineMock(return_value=True) + instance.async_setup = AsyncMock(return_value=True) instance.home.id = "1" instance.home.modelType = "mock-type" instance.home.name = "mock-name" instance.home.currentAPVersion = "mock-ap-version" - instance.async_reset = CoroutineMock(return_value=True) + instance.async_reset = AsyncMock(return_value=True) assert await async_setup_component(hass, HMIPC_DOMAIN, {}) @@ -181,12 +181,12 @@ async def test_setup_services_and_unload_services(hass): with patch("homeassistant.components.homematicip_cloud.HomematicipHAP") as mock_hap: instance = mock_hap.return_value - instance.async_setup = CoroutineMock(return_value=True) + instance.async_setup = AsyncMock(return_value=True) instance.home.id = "1" instance.home.modelType = "mock-type" instance.home.name = "mock-name" instance.home.currentAPVersion = "mock-ap-version" - instance.async_reset = CoroutineMock(return_value=True) + instance.async_reset = AsyncMock(return_value=True) assert await async_setup_component(hass, HMIPC_DOMAIN, {}) @@ -214,12 +214,12 @@ async def test_setup_two_haps_unload_one_by_one(hass): with patch("homeassistant.components.homematicip_cloud.HomematicipHAP") as mock_hap: instance = mock_hap.return_value - instance.async_setup = CoroutineMock(return_value=True) + instance.async_setup = AsyncMock(return_value=True) instance.home.id = "1" instance.home.modelType = "mock-type" instance.home.name = "mock-name" instance.home.currentAPVersion = "mock-ap-version" - instance.async_reset = CoroutineMock(return_value=True) + instance.async_reset = AsyncMock(return_value=True) assert await async_setup_component(hass, HMIPC_DOMAIN, {}) diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index ddf08de42b4..d13581e12a2 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -7,7 +7,6 @@ from unittest.mock import Mock, mock_open from aiohttp import web from aiohttp.web_exceptions import HTTPUnauthorized from aiohttp.web_middlewares import middleware -from asynctest import patch import pytest import homeassistant.components.http as http @@ -25,6 +24,8 @@ from homeassistant.setup import async_setup_component from . import mock_real_ip +from tests.async_mock import patch + SUPERVISOR_IP = "1.2.3.4" BANNED_IPS = ["200.201.202.203", "100.64.0.2"] BANNED_IPS_WITH_SUPERVISOR = BANNED_IPS + [SUPERVISOR_IP] diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py index 780c77e0196..385097514f8 100644 --- a/tests/components/hue/test_bridge.py +++ b/tests/components/hue/test_bridge.py @@ -1,15 +1,16 @@ """Test Hue bridge.""" -from asynctest import CoroutineMock, Mock, patch import pytest from homeassistant.components.hue import bridge, errors from homeassistant.exceptions import ConfigEntryNotReady +from tests.async_mock import AsyncMock, Mock, patch + async def test_bridge_setup(hass): """Test a successful setup.""" entry = Mock() - api = Mock(initialize=CoroutineMock()) + api = Mock(initialize=AsyncMock()) entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) @@ -92,7 +93,7 @@ async def test_reset_unloads_entry_if_setup(hass): async def test_handle_unauthorized(hass): """Test handling an unauthorized error on update.""" - entry = Mock(async_setup=CoroutineMock()) + entry = Mock(async_setup=AsyncMock()) entry.data = {"host": "1.2.3.4", "username": "mock-username"} hue_bridge = bridge.HueBridge(hass, entry, False, False) diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 87d4dc2b887..b5ea2e4e0ea 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -5,7 +5,6 @@ from unittest.mock import Mock from aiohttp import client_exceptions import aiohue from aiohue.discovery import URL_NUPNP -from asynctest import CoroutineMock, patch import pytest import voluptuous as vol @@ -13,6 +12,7 @@ from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.hue import config_flow, const +from tests.async_mock import AsyncMock, patch from tests.common import MockConfigEntry @@ -41,7 +41,7 @@ def get_mock_bridge( mock_create_user = create_user mock_bridge.create_user = mock_create_user - mock_bridge.initialize = CoroutineMock() + mock_bridge.initialize = AsyncMock() return mock_bridge @@ -190,7 +190,7 @@ async def test_flow_timeout_discovery(hass): async def test_flow_link_timeout(hass): """Test config flow.""" mock_bridge = get_mock_bridge( - mock_create_user=CoroutineMock(side_effect=asyncio.TimeoutError), + mock_create_user=AsyncMock(side_effect=asyncio.TimeoutError), ) with patch( "homeassistant.components.hue.config_flow.discover_nupnp", @@ -211,7 +211,7 @@ async def test_flow_link_timeout(hass): async def test_flow_link_unknown_error(hass): """Test if a unknown error happened during the linking processes.""" - mock_bridge = get_mock_bridge(mock_create_user=CoroutineMock(side_effect=OSError),) + mock_bridge = get_mock_bridge(mock_create_user=AsyncMock(side_effect=OSError),) with patch( "homeassistant.components.hue.config_flow.discover_nupnp", return_value=[mock_bridge], @@ -232,7 +232,7 @@ async def test_flow_link_unknown_error(hass): async def test_flow_link_button_not_pressed(hass): """Test config flow .""" mock_bridge = get_mock_bridge( - mock_create_user=CoroutineMock(side_effect=aiohue.LinkButtonNotPressed), + mock_create_user=AsyncMock(side_effect=aiohue.LinkButtonNotPressed), ) with patch( "homeassistant.components.hue.config_flow.discover_nupnp", @@ -254,7 +254,7 @@ async def test_flow_link_button_not_pressed(hass): async def test_flow_link_unknown_host(hass): """Test config flow .""" mock_bridge = get_mock_bridge( - mock_create_user=CoroutineMock(side_effect=client_exceptions.ClientOSError), + mock_create_user=AsyncMock(side_effect=client_exceptions.ClientOSError), ) with patch( "homeassistant.components.hue.config_flow.discover_nupnp", diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py index 51ea3f2ae71..a144902bbc8 100644 --- a/tests/components/hue/test_init.py +++ b/tests/components/hue/test_init.py @@ -1,11 +1,10 @@ """Test Hue setup process.""" from unittest.mock import Mock -from asynctest import CoroutineMock, patch - from homeassistant.components import hue from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock, patch from tests.common import MockConfigEntry, mock_coro @@ -102,9 +101,9 @@ async def test_config_passed_to_config_entry(hass): mock_registry = Mock() with patch.object(hue, "HueBridge") as mock_bridge, patch( "homeassistant.helpers.device_registry.async_get_registry", - return_value=mock_coro(mock_registry), + return_value=mock_registry, ): - mock_bridge.return_value.async_setup.return_value = mock_coro(True) + mock_bridge.return_value.async_setup = AsyncMock(return_value=True) mock_bridge.return_value.api.config = Mock( mac="mock-mac", bridgeid="mock-bridgeid", @@ -159,13 +158,13 @@ async def test_unload_entry(hass): "homeassistant.helpers.device_registry.async_get_registry", return_value=mock_coro(Mock()), ): - mock_bridge.return_value.async_setup.return_value = mock_coro(True) + mock_bridge.return_value.async_setup = AsyncMock(return_value=True) mock_bridge.return_value.api.config = Mock(bridgeid="aabbccddeeff") assert await async_setup_component(hass, hue.DOMAIN, {}) is True assert len(mock_bridge.return_value.mock_calls) == 1 - mock_bridge.return_value.async_reset.return_value = mock_coro(True) + mock_bridge.return_value.async_reset = AsyncMock(return_value=True) assert await hue.async_unload_entry(hass, entry) assert len(mock_bridge.return_value.async_reset.mock_calls) == 1 assert hass.data[hue.DOMAIN] == {} @@ -180,7 +179,7 @@ async def test_setting_unique_id(hass): "homeassistant.helpers.device_registry.async_get_registry", return_value=mock_coro(Mock()), ): - mock_bridge.return_value.async_setup.return_value = mock_coro(True) + mock_bridge.return_value.async_setup = AsyncMock(return_value=True) mock_bridge.return_value.api.config = Mock(bridgeid="mock-id") assert await async_setup_component(hass, hue.DOMAIN, {}) is True @@ -201,7 +200,7 @@ async def test_security_vuln_check(hass): "HueBridge", Mock( return_value=Mock( - async_setup=CoroutineMock(return_value=True), api=Mock(config=config) + async_setup=AsyncMock(return_value=True), api=Mock(config=config) ) ), ): diff --git a/tests/components/hunterdouglas_powerview/test_config_flow.py b/tests/components/hunterdouglas_powerview/test_config_flow.py index 67b2c2dc954..e5b0d472304 100644 --- a/tests/components/hunterdouglas_powerview/test_config_flow.py +++ b/tests/components/hunterdouglas_powerview/test_config_flow.py @@ -2,11 +2,10 @@ import asyncio import json -from asynctest import CoroutineMock, MagicMock, patch - from homeassistant import config_entries, setup from homeassistant.components.hunterdouglas_powerview.const import DOMAIN +from tests.async_mock import AsyncMock, MagicMock, patch from tests.common import load_fixture @@ -15,13 +14,11 @@ def _get_mock_powerview_userdata(userdata=None, get_resources=None): if not userdata: userdata = json.loads(load_fixture("hunterdouglas_powerview/userdata.json")) if get_resources: - type(mock_powerview_userdata).get_resources = CoroutineMock( + type(mock_powerview_userdata).get_resources = AsyncMock( side_effect=get_resources ) else: - type(mock_powerview_userdata).get_resources = CoroutineMock( - return_value=userdata - ) + type(mock_powerview_userdata).get_resources = AsyncMock(return_value=userdata) return mock_powerview_userdata diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 399523dd779..a2a2ec2d7a6 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,8 +1,6 @@ """The tests for the image_processing component.""" from unittest.mock import PropertyMock -from asynctest import patch - import homeassistant.components.http as http import homeassistant.components.image_processing as ip from homeassistant.const import ATTR_ENTITY_PICTURE @@ -10,6 +8,7 @@ from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import ( assert_setup_component, get_test_home_assistant, diff --git a/tests/components/ipma/test_config_flow.py b/tests/components/ipma/test_config_flow.py index fd44f8b2a58..b3b8968f451 100644 --- a/tests/components/ipma/test_config_flow.py +++ b/tests/components/ipma/test_config_flow.py @@ -1,10 +1,9 @@ """Tests for IPMA config flow.""" -from unittest.mock import Mock, patch from homeassistant.components.ipma import config_flow from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE -from tests.common import mock_coro +from tests.async_mock import Mock, patch async def test_show_config_form(): @@ -58,8 +57,8 @@ async def test_flow_show_form(): flow = config_flow.IpmaFlowHandler() flow.hass = hass - with patch.object( - flow, "_show_config_form", return_value=mock_coro() + with patch( + "homeassistant.components.ipma.config_flow.IpmaFlowHandler._show_config_form" ) as config_form: await flow.async_step_user() assert len(config_form.mock_calls) == 1 @@ -77,10 +76,10 @@ async def test_flow_entry_created_from_user_input(): test_data = {"name": "home", CONF_LONGITUDE: "0", CONF_LATITUDE: "0"} # Test that entry created when user_input name not exists - with patch.object( - flow, "_show_config_form", return_value=mock_coro() + with patch( + "homeassistant.components.ipma.config_flow.IpmaFlowHandler._show_config_form" ) as config_form, patch.object( - flow.hass.config_entries, "async_entries", return_value=mock_coro() + flow.hass.config_entries, "async_entries", return_value=[], ) as config_entries: result = await flow.async_step_user(user_input=test_data) @@ -104,8 +103,8 @@ async def test_flow_entry_config_entry_already_exists(): test_data = {"name": "home", CONF_LONGITUDE: "0", CONF_LATITUDE: "0"} # Test that entry created when user_input name not exists - with patch.object( - flow, "_show_config_form", return_value=mock_coro() + with patch( + "homeassistant.components.ipma.config_flow.IpmaFlowHandler._show_config_form" ) as config_form, patch.object( flow.hass.config_entries, "async_entries", return_value={"home": test_data} ) as config_entries: diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index b3d398377f0..e7542070d2c 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -1,8 +1,6 @@ """The tests for the IPMA weather component.""" from collections import namedtuple -from asynctest import patch - from homeassistant.components import weather from homeassistant.components.weather import ( ATTR_FORECAST, @@ -23,6 +21,7 @@ from homeassistant.components.weather import ( from homeassistant.setup import async_setup_component from homeassistant.util.dt import now +from tests.async_mock import patch from tests.common import MockConfigEntry TEST_CONFIG = {"name": "HomeTown", "latitude": "40.00", "longitude": "-8.00"} diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index e6830f559c6..51caadfceb3 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -1,14 +1,13 @@ """Tests for the IPP sensor platform.""" from datetime import datetime -from asynctest import patch - 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, UNIT_PERCENTAGE from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.components.ipp import init_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/izone/test_config_flow.py b/tests/components/izone/test_config_flow.py index 942d95cc503..72b80e75bcf 100644 --- a/tests/components/izone/test_config_flow.py +++ b/tests/components/izone/test_config_flow.py @@ -1,11 +1,12 @@ """Tests for iZone.""" -from asynctest import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow from homeassistant.components.izone.const import DISPATCH_CONTROLLER_DISCOVERED, IZONE +from tests.async_mock import Mock, patch + @pytest.fixture def mock_disco(): diff --git a/tests/components/konnected/test_config_flow.py b/tests/components/konnected/test_config_flow.py index 0bf6e7846ae..a0d870b37ff 100644 --- a/tests/components/konnected/test_config_flow.py +++ b/tests/components/konnected/test_config_flow.py @@ -1,10 +1,10 @@ """Tests for Konnected Alarm Panel config flow.""" -from asynctest import patch import pytest from homeassistant.components import konnected from homeassistant.components.konnected import config_flow +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/konnected/test_init.py b/tests/components/konnected/test_init.py index f87c66fe412..1bdd1278b93 100644 --- a/tests/components/konnected/test_init.py +++ b/tests/components/konnected/test_init.py @@ -1,5 +1,4 @@ """Test Konnected setup process.""" -from asynctest import patch import pytest from homeassistant.components import konnected @@ -7,6 +6,7 @@ from homeassistant.components.konnected import config_flow from homeassistant.const import HTTP_NOT_FOUND from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/konnected/test_panel.py b/tests/components/konnected/test_panel.py index f1ae8a4357c..f167c558d01 100644 --- a/tests/components/konnected/test_panel.py +++ b/tests/components/konnected/test_panel.py @@ -1,10 +1,10 @@ """Test Konnected setup process.""" -from asynctest import patch import pytest from homeassistant.components.konnected import config_flow, panel from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 814e304f9f5..abe6f6ec515 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -5,7 +5,6 @@ from functools import partial import logging import unittest -from asynctest import patch import pytest import voluptuous as vol @@ -28,6 +27,7 @@ import homeassistant.core as ha from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import get_test_home_assistant, init_recorder_component from tests.components.recorder.common import trigger_db_commit diff --git a/tests/components/logi_circle/test_config_flow.py b/tests/components/logi_circle/test_config_flow.py index 7ba3816b5e2..43dea15f7e6 100644 --- a/tests/components/logi_circle/test_config_flow.py +++ b/tests/components/logi_circle/test_config_flow.py @@ -13,6 +13,7 @@ from homeassistant.components.logi_circle.config_flow import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock from tests.common import mock_coro @@ -51,8 +52,8 @@ def mock_logi_circle(): "homeassistant.components.logi_circle.config_flow.LogiCircle" ) as logi_circle: LogiCircle = logi_circle() - LogiCircle.authorize = Mock(return_value=mock_coro(return_value=True)) - LogiCircle.close = Mock(return_value=mock_coro(return_value=True)) + LogiCircle.authorize = AsyncMock(return_value=True) + LogiCircle.close = AsyncMock(return_value=True) LogiCircle.account = mock_coro(return_value={"accountId": "testId"}) LogiCircle.authorize_url = "http://authorize.url" yield LogiCircle diff --git a/tests/components/lovelace/test_resources.py b/tests/components/lovelace/test_resources.py index a44af14d3a0..d32dc9388f1 100644 --- a/tests/components/lovelace/test_resources.py +++ b/tests/components/lovelace/test_resources.py @@ -2,11 +2,11 @@ import copy import uuid -from asynctest import patch - from homeassistant.components.lovelace import dashboard, resources from homeassistant.setup import async_setup_component +from tests.async_mock import patch + RESOURCE_EXAMPLES = [ {"type": "js", "url": "/local/bla.js"}, {"type": "css", "url": "/local/bla.css"}, diff --git a/tests/components/luftdaten/test_config_flow.py b/tests/components/luftdaten/test_config_flow.py index e0e54a6b790..70b306d34c0 100644 --- a/tests/components/luftdaten/test_config_flow.py +++ b/tests/components/luftdaten/test_config_flow.py @@ -1,13 +1,12 @@ """Define tests for the Luftdaten config flow.""" from datetime import timedelta -from asynctest import patch - from homeassistant import data_entry_flow from homeassistant.components.luftdaten import DOMAIN, config_flow from homeassistant.components.luftdaten.const import CONF_SENSOR_ID from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 330581d2d14..50924af5d76 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -1,13 +1,11 @@ """Test the base functions of the media player.""" import base64 -from asynctest import patch - from homeassistant.components import media_player from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import patch async def test_get_image(hass, hass_ws_client, caplog): @@ -21,7 +19,7 @@ async def test_get_image(hass, hass_ws_client, caplog): with patch( "homeassistant.components.media_player.MediaPlayerEntity." "async_get_media_image", - return_value=mock_coro((b"image", "image/jpeg")), + return_value=(b"image", "image/jpeg"), ): await client.send_json( { diff --git a/tests/components/melcloud/test_config_flow.py b/tests/components/melcloud/test_config_flow.py index c936807484a..e6b36306986 100644 --- a/tests/components/melcloud/test_config_flow.py +++ b/tests/components/melcloud/test_config_flow.py @@ -2,7 +2,6 @@ import asyncio from aiohttp import ClientError, ClientResponseError -from asynctest import patch import pymelcloud import pytest @@ -10,6 +9,7 @@ from homeassistant import config_entries from homeassistant.components.melcloud.const import DOMAIN from homeassistant.const import HTTP_FORBIDDEN, HTTP_INTERNAL_SERVER_ERROR +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/met/conftest.py b/tests/components/met/conftest.py index e475889863d..164a8498465 100644 --- a/tests/components/met/conftest.py +++ b/tests/components/met/conftest.py @@ -1,9 +1,7 @@ """Fixtures for Met weather testing.""" -from unittest.mock import patch - import pytest -from tests.common import mock_coro +from tests.async_mock import AsyncMock, patch @pytest.fixture @@ -11,7 +9,7 @@ def mock_weather(): """Mock weather data.""" with patch("metno.MetWeatherData") as mock_data: mock_data = mock_data.return_value - mock_data.fetching_data.side_effect = lambda: mock_coro(True) + mock_data.fetching_data = AsyncMock(return_value=True) mock_data.get_current_weather.return_value = { "condition": "cloudy", "temperature": 15, diff --git a/tests/components/met/test_config_flow.py b/tests/components/met/test_config_flow.py index 980994f3fb2..a4c48f38245 100644 --- a/tests/components/met/test_config_flow.py +++ b/tests/components/met/test_config_flow.py @@ -1,10 +1,10 @@ """Tests for Met.no config flow.""" -from asynctest import patch import pytest from homeassistant.components.met.const import DOMAIN, HOME_LOCATION_NAME from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/microsoft_face/test_init.py b/tests/components/microsoft_face/test_init.py index 478a3fb29bd..abd35eca3e1 100644 --- a/tests/components/microsoft_face/test_init.py +++ b/tests/components/microsoft_face/test_init.py @@ -1,8 +1,6 @@ """The tests for the microsoft face platform.""" import asyncio -from asynctest import patch - from homeassistant.components import camera, microsoft_face as mf from homeassistant.components.microsoft_face import ( ATTR_CAMERA_ENTITY, @@ -19,6 +17,7 @@ from homeassistant.components.microsoft_face import ( from homeassistant.const import ATTR_NAME from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant, load_fixture diff --git a/tests/components/mikrotik/test_hub.py b/tests/components/mikrotik/test_hub.py index 9a2f75f7015..ecfb9add717 100644 --- a/tests/components/mikrotik/test_hub.py +++ b/tests/components/mikrotik/test_hub.py @@ -1,5 +1,4 @@ """Test Mikrotik hub.""" -from asynctest import patch import librouteros from homeassistant import config_entries @@ -7,6 +6,7 @@ from homeassistant.components import mikrotik from . import ARP_DATA, DHCP_DATA, MOCK_DATA, MOCK_OPTIONS, WIRELESS_DATA +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/mikrotik/test_init.py b/tests/components/mikrotik/test_init.py index 1a634916781..b3dca7269eb 100644 --- a/tests/components/mikrotik/test_init.py +++ b/tests/components/mikrotik/test_init.py @@ -1,11 +1,10 @@ """Test Mikrotik setup process.""" -from asynctest import CoroutineMock, Mock, patch - from homeassistant.components import mikrotik from homeassistant.setup import async_setup_component from . import MOCK_DATA +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry @@ -25,7 +24,7 @@ async def test_successful_config_entry(hass): "homeassistant.helpers.device_registry.async_get_registry", return_value=mock_registry, ): - mock_hub.return_value.async_setup = CoroutineMock(return_value=True) + mock_hub.return_value.async_setup = AsyncMock(return_value=True) mock_hub.return_value.serial_num = "12345678" mock_hub.return_value.model = "RB750" mock_hub.return_value.hostname = "mikrotik" @@ -55,7 +54,7 @@ async def test_hub_fail_setup(hass): entry.add_to_hass(hass) with patch.object(mikrotik, "MikrotikHub") as mock_hub: - mock_hub.return_value.async_setup = CoroutineMock(return_value=False) + mock_hub.return_value.async_setup = AsyncMock(return_value=False) assert await mikrotik.async_setup_entry(hass, entry) is False assert mikrotik.DOMAIN not in hass.data @@ -69,7 +68,7 @@ async def test_unload_entry(hass): with patch.object(mikrotik, "MikrotikHub") as mock_hub, patch( "homeassistant.helpers.device_registry.async_get_registry", return_value=Mock(), ): - mock_hub.return_value.async_setup = CoroutineMock(return_value=True) + mock_hub.return_value.async_setup = AsyncMock(return_value=True) mock_hub.return_value.serial_num = "12345678" mock_hub.return_value.model = "RB750" mock_hub.return_value.hostname = "mikrotik" diff --git a/tests/components/minecraft_server/test_config_flow.py b/tests/components/minecraft_server/test_config_flow.py index 7db6dc33b5a..17ec9080e86 100644 --- a/tests/components/minecraft_server/test_config_flow.py +++ b/tests/components/minecraft_server/test_config_flow.py @@ -3,7 +3,6 @@ import asyncio import aiodns -from asynctest import patch from mcstatus.pinger import PingResponse from homeassistant.components.minecraft_server.const import ( @@ -20,6 +19,7 @@ from homeassistant.data_entry_flow import ( ) from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index 4397f446a19..a1f3e107152 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -3,7 +3,6 @@ import asyncio import json from unittest.mock import MagicMock -from asynctest import call, patch import pytest from homeassistant.components.minio import ( @@ -20,6 +19,7 @@ from homeassistant.components.minio import ( from homeassistant.core import callback from homeassistant.setup import async_setup_component +from tests.async_mock import call, patch from tests.components.minio.common import TEST_EVENT diff --git a/tests/components/monoprice/test_config_flow.py b/tests/components/monoprice/test_config_flow.py index ecafa17e174..8c6e2a3916c 100644 --- a/tests/components/monoprice/test_config_flow.py +++ b/tests/components/monoprice/test_config_flow.py @@ -1,5 +1,4 @@ """Test the Monoprice 6-Zone Amplifier config flow.""" -from asynctest import patch from serial import SerialException from homeassistant import config_entries, data_entry_flow, setup @@ -12,6 +11,7 @@ from homeassistant.components.monoprice.const import ( ) from homeassistant.const import CONF_PORT +from tests.async_mock import patch from tests.common import MockConfigEntry CONFIG = { diff --git a/tests/components/monoprice/test_media_player.py b/tests/components/monoprice/test_media_player.py index 0006364b94e..f70a19f51fc 100644 --- a/tests/components/monoprice/test_media_player.py +++ b/tests/components/monoprice/test_media_player.py @@ -1,7 +1,6 @@ """The tests for Monoprice Media player platform.""" from collections import defaultdict -from asynctest import patch from serial import SerialException from homeassistant.components.media_player.const import ( @@ -34,6 +33,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.entity_component import async_update_entity +from tests.async_mock import patch from tests.common import MockConfigEntry MOCK_CONFIG = {CONF_PORT: "fake port", CONF_SOURCES: {"1": "one", "3": "three"}} diff --git a/tests/components/mqtt/test_device_tracker.py b/tests/components/mqtt/test_device_tracker.py index ababe8395f3..32f826422a8 100644 --- a/tests/components/mqtt/test_device_tracker.py +++ b/tests/components/mqtt/test_device_tracker.py @@ -1,11 +1,11 @@ """The tests for the MQTT device tracker platform.""" -from asynctest import patch import pytest from homeassistant.components.device_tracker.const import DOMAIN, SOURCE_TYPE_BLUETOOTH from homeassistant.const import CONF_PLATFORM, STATE_HOME, STATE_NOT_HOME from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import async_fire_mqtt_message diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 1b2f76d6c5e..9d8ede4f516 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -13,10 +13,10 @@ from homeassistant.components.mqtt.abbreviations import ( from homeassistant.components.mqtt.discovery import ALREADY_DISCOVERED, async_start from homeassistant.const import STATE_OFF, STATE_ON +from tests.async_mock import AsyncMock from tests.common import ( MockConfigEntry, async_fire_mqtt_message, - mock_coro, mock_device_registry, mock_registry, ) @@ -57,7 +57,7 @@ async def test_invalid_topic(hass, mqtt_mock): domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"} ) - mock_dispatcher_send.return_value = mock_coro() + mock_dispatcher_send = AsyncMock(return_value=None) await async_start(hass, "homeassistant", {}, entry) async_fire_mqtt_message( @@ -76,7 +76,7 @@ async def test_invalid_json(hass, mqtt_mock, caplog): domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"} ) - mock_dispatcher_send.return_value = mock_coro() + mock_dispatcher_send = AsyncMock(return_value=None) await async_start(hass, "homeassistant", {}, entry) async_fire_mqtt_message( @@ -96,7 +96,7 @@ async def test_only_valid_components(hass, mqtt_mock, caplog): invalid_component = "timer" - mock_dispatcher_send.return_value = mock_coro() + mock_dispatcher_send = AsyncMock(return_value=None) await async_start(hass, "homeassistant", {}, entry) async_fire_mqtt_message( diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 207529c2ad3..a139a942530 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -4,7 +4,6 @@ import json import ssl import unittest -from asynctest import CoroutineMock, MagicMock, call, mock_open, patch import pytest import voluptuous as vol @@ -24,6 +23,7 @@ from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow +from tests.async_mock import AsyncMock, MagicMock, call, mock_open, patch from tests.common import ( MockConfigEntry, async_fire_mqtt_message, @@ -60,8 +60,8 @@ def entity_reg(hass): def mock_mqtt(): """Make sure connection is established.""" with patch("homeassistant.components.mqtt.MQTT") as mock_mqtt: - mock_mqtt.return_value.async_connect = CoroutineMock(return_value=True) - mock_mqtt.return_value.async_disconnect = CoroutineMock(return_value=True) + mock_mqtt.return_value.async_connect = AsyncMock(return_value=True) + mock_mqtt.return_value.async_disconnect = AsyncMock(return_value=True) yield mock_mqtt diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 205e7400eb3..ccf5935cecc 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -153,8 +153,6 @@ light: payload_off: "off" """ -from asynctest import call, patch - from homeassistant.components import light, mqtt from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON @@ -183,6 +181,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import call, patch from tests.common import ( MockConfigEntry, assert_setup_component, diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 1e3ac34af89..d4712e9f835 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -89,8 +89,6 @@ light: """ import json -from asynctest import call, patch - from homeassistant.components import light from homeassistant.const import ( ATTR_ASSUMED_STATE, @@ -123,6 +121,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import call, patch from tests.common import async_fire_mqtt_message from tests.components.light import common diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index 20b5ecefd89..29adc555bc5 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -26,8 +26,6 @@ If your light doesn't support white value feature, omit `white_value_template`. If your light doesn't support RGB feature, omit `(red|green|blue)_template`. """ -from asynctest import patch - from homeassistant.components import light from homeassistant.const import ( ATTR_ASSUMED_STATE, @@ -60,6 +58,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import assert_setup_component, async_fire_mqtt_message from tests.components.light import common diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py index 3186b3a2734..b3320d6aaca 100644 --- a/tests/components/mqtt/test_server.py +++ b/tests/components/mqtt/test_server.py @@ -1,13 +1,13 @@ """The tests for the MQTT component embedded server.""" from unittest.mock import MagicMock, Mock -from asynctest import CoroutineMock, patch import pytest import homeassistant.components.mqtt as mqtt from homeassistant.const import CONF_PASSWORD from homeassistant.setup import setup_component +from tests.async_mock import AsyncMock, patch from tests.common import get_test_home_assistant, mock_coro @@ -29,15 +29,15 @@ class TestMQTT: @patch("passlib.apps.custom_app_context", Mock(return_value="")) @patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock())) - @patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=CoroutineMock()))) - @patch("hbmqtt.broker.Broker.start", Mock(return_value=mock_coro())) + @patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=AsyncMock()))) + @patch("hbmqtt.broker.Broker.start", AsyncMock(return_value=None)) @patch("homeassistant.components.mqtt.MQTT") def test_creating_config_with_pass_and_no_http_pass(self, mock_mqtt): """Test if the MQTT server gets started with password. Since 0.77, MQTT server has to set up its own password. """ - mock_mqtt().async_connect.return_value = mock_coro(True) + mock_mqtt().async_connect = AsyncMock(return_value=True) self.hass.bus.listen_once = MagicMock() password = "mqtt_secret" @@ -51,15 +51,15 @@ class TestMQTT: @patch("passlib.apps.custom_app_context", Mock(return_value="")) @patch("tempfile.NamedTemporaryFile", Mock(return_value=MagicMock())) - @patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=CoroutineMock()))) - @patch("hbmqtt.broker.Broker.start", Mock(return_value=mock_coro())) + @patch("hbmqtt.broker.Broker", Mock(return_value=MagicMock(start=AsyncMock()))) + @patch("hbmqtt.broker.Broker.start", AsyncMock(return_value=None)) @patch("homeassistant.components.mqtt.MQTT") def test_creating_config_with_pass_and_http_pass(self, mock_mqtt): """Test if the MQTT server gets started with password. Since 0.77, MQTT server has to set up its own password. """ - mock_mqtt().async_connect.return_value = mock_coro(True) + mock_mqtt().async_connect = AsyncMock(return_value=True) self.hass.bus.listen_once = MagicMock() password = "mqtt_secret" diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index 1aaeb154dc2..c34d32a3c9d 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -1,5 +1,4 @@ """The tests for the MQTT switch platform.""" -from asynctest import patch import pytest from homeassistant.components import switch @@ -29,7 +28,8 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) -from tests.common import async_fire_mqtt_message, async_mock_mqtt_component, mock_coro +from tests.async_mock import patch +from tests.common import async_fire_mqtt_message, async_mock_mqtt_component from tests.components.switch import common DEFAULT_CONFIG = { @@ -81,7 +81,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mock_publish): with patch( "homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state", - return_value=mock_coro(fake_state), + return_value=fake_state, ): assert await async_setup_component( hass, diff --git a/tests/components/mqtt_json/test_device_tracker.py b/tests/components/mqtt_json/test_device_tracker.py index 9efff135fe2..864b3c232ed 100644 --- a/tests/components/mqtt_json/test_device_tracker.py +++ b/tests/components/mqtt_json/test_device_tracker.py @@ -3,7 +3,6 @@ import json import logging import os -from asynctest import patch import pytest from homeassistant.components.device_tracker.legacy import ( @@ -13,6 +12,7 @@ from homeassistant.components.device_tracker.legacy import ( from homeassistant.const import CONF_PLATFORM from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import async_fire_mqtt_message, async_mock_mqtt_component _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/myq/test_config_flow.py b/tests/components/myq/test_config_flow.py index 7620a9ad176..ed022df0dd7 100644 --- a/tests/components/myq/test_config_flow.py +++ b/tests/components/myq/test_config_flow.py @@ -1,11 +1,11 @@ """Test the MyQ config flow.""" -from asynctest import patch from pymyq.errors import InvalidCredentialsError, MyQError from homeassistant import config_entries, setup from homeassistant.components.myq.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/myq/util.py b/tests/components/myq/util.py index 7cff7bd2af9..61e49a98b83 100644 --- a/tests/components/myq/util.py +++ b/tests/components/myq/util.py @@ -2,12 +2,11 @@ import json -from asynctest import patch - from homeassistant.components.myq.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from tests.async_mock import patch from tests.common import MockConfigEntry, load_fixture diff --git a/tests/components/mythicbeastsdns/test_init.py b/tests/components/mythicbeastsdns/test_init.py index ee037a029ed..e8efac2c01d 100644 --- a/tests/components/mythicbeastsdns/test_init.py +++ b/tests/components/mythicbeastsdns/test_init.py @@ -1,11 +1,11 @@ """Test the Mythic Beasts DNS component.""" import logging -import asynctest - from homeassistant.components import mythicbeastsdns from homeassistant.setup import async_setup_component +from tests.async_mock import patch + _LOGGER = logging.getLogger(__name__) @@ -20,7 +20,7 @@ async def mbddns_update_mock(domain, password, host, ttl=60, session=None): return True -@asynctest.mock.patch("mbddns.update", new=mbddns_update_mock) +@patch("mbddns.update", new=mbddns_update_mock) async def test_update(hass): """Run with correct values and check true is returned.""" result = await async_setup_component( @@ -37,7 +37,7 @@ async def test_update(hass): assert result -@asynctest.mock.patch("mbddns.update", new=mbddns_update_mock) +@patch("mbddns.update", new=mbddns_update_mock) async def test_update_fails_if_wrong_token(hass): """Run with incorrect token and check false is returned.""" result = await async_setup_component( @@ -54,7 +54,7 @@ async def test_update_fails_if_wrong_token(hass): assert not result -@asynctest.mock.patch("mbddns.update", new=mbddns_update_mock) +@patch("mbddns.update", new=mbddns_update_mock) async def test_update_fails_if_invalid_host(hass): """Run with invalid characters in host and check false is returned.""" result = await async_setup_component( diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 9da361852e9..f959b5345dd 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -1,7 +1,6 @@ """Tests for the ness_alarm component.""" from enum import Enum -from asynctest import MagicMock, patch import pytest from homeassistant.components import alarm_control_panel @@ -32,6 +31,8 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + VALID_CONFIG = { DOMAIN: { CONF_HOST: "alarm.local", diff --git a/tests/components/nest/test_config_flow.py b/tests/components/nest/test_config_flow.py index ec6218fb0d7..6c0c0197a74 100644 --- a/tests/components/nest/test_config_flow.py +++ b/tests/components/nest/test_config_flow.py @@ -6,6 +6,7 @@ from homeassistant import data_entry_flow from homeassistant.components.nest import DOMAIN, config_flow from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock from tests.common import mock_coro @@ -33,8 +34,8 @@ async def test_abort_if_already_setup(hass): async def test_full_flow_implementation(hass): """Test registering an implementation and finishing flow works.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) - convert_code = Mock(return_value=mock_coro({"access_token": "yoo"})) + gen_authorize_url = AsyncMock(return_value="https://example.com") + convert_code = AsyncMock(return_value={"access_token": "yoo"}) config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, convert_code ) @@ -62,7 +63,7 @@ async def test_full_flow_implementation(hass): async def test_not_pick_implementation_if_only_one(hass): """Test we allow picking implementation if we have two.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) + gen_authorize_url = AsyncMock(return_value="https://example.com") config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, None ) @@ -104,7 +105,7 @@ async def test_abort_if_exception_generating_auth_url(hass): async def test_verify_code_timeout(hass): """Test verify code timing out.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) + gen_authorize_url = AsyncMock(return_value="https://example.com") convert_code = Mock(side_effect=asyncio.TimeoutError) config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, convert_code @@ -124,7 +125,7 @@ async def test_verify_code_timeout(hass): async def test_verify_code_invalid(hass): """Test verify code invalid.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) + gen_authorize_url = AsyncMock(return_value="https://example.com") convert_code = Mock(side_effect=config_flow.CodeInvalid) config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, convert_code @@ -144,7 +145,7 @@ async def test_verify_code_invalid(hass): async def test_verify_code_unknown_error(hass): """Test verify code unknown error.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) + gen_authorize_url = AsyncMock(return_value="https://example.com") convert_code = Mock(side_effect=config_flow.NestAuthError) config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, convert_code @@ -164,7 +165,7 @@ async def test_verify_code_unknown_error(hass): async def test_verify_code_exception(hass): """Test verify code blows up.""" - gen_authorize_url = Mock(return_value=mock_coro("https://example.com")) + gen_authorize_url = AsyncMock(return_value="https://example.com") convert_code = Mock(side_effect=ValueError) config_flow.register_flow_implementation( hass, "test", "Test", gen_authorize_url, convert_code diff --git a/tests/components/netatmo/test_config_flow.py b/tests/components/netatmo/test_config_flow.py index 29a1d4f53d5..047dd4c0c40 100644 --- a/tests/components/netatmo/test_config_flow.py +++ b/tests/components/netatmo/test_config_flow.py @@ -1,6 +1,4 @@ """Test the Netatmo config flow.""" -from asynctest import patch - from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.netatmo import config_flow from homeassistant.components.netatmo.const import ( @@ -10,6 +8,7 @@ from homeassistant.components.netatmo.const import ( ) from homeassistant.helpers import config_entry_oauth2_flow +from tests.async_mock import patch from tests.common import MockConfigEntry CLIENT_ID = "1234" diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index ff6f8590287..0dce512cff4 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -1,12 +1,12 @@ """Test the nexia config flow.""" -from asynctest import patch -from asynctest.mock import MagicMock from requests.exceptions import ConnectTimeout from homeassistant import config_entries, setup from homeassistant.components.nexia.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import MagicMock, patch + async def test_form(hass): """Test we get the form.""" diff --git a/tests/components/nexia/util.py b/tests/components/nexia/util.py index cc2b11afcbe..2da56d50f37 100644 --- a/tests/components/nexia/util.py +++ b/tests/components/nexia/util.py @@ -1,7 +1,6 @@ """Tests for the nexia integration.""" import uuid -from asynctest import patch from nexia.home import NexiaHome import requests_mock @@ -9,6 +8,7 @@ from homeassistant.components.nexia.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant +from tests.async_mock import patch from tests.common import MockConfigEntry, load_fixture diff --git a/tests/components/notion/test_config_flow.py b/tests/components/notion/test_config_flow.py index 7d3ddb1cf4b..6aaf7df9505 100644 --- a/tests/components/notion/test_config_flow.py +++ b/tests/components/notion/test_config_flow.py @@ -1,6 +1,5 @@ """Define tests for the Notion config flow.""" import aionotion -from asynctest import patch import pytest from homeassistant import data_entry_flow @@ -8,20 +7,21 @@ from homeassistant.components.notion import DOMAIN, config_flow from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import AsyncMock, patch +from tests.common import MockConfigEntry @pytest.fixture -def mock_client_coro(): +def mock_client(): """Define a fixture for a client creation coroutine.""" - return mock_coro() + return AsyncMock(return_value=None) @pytest.fixture -def mock_aionotion(mock_client_coro): +def mock_aionotion(mock_client): """Mock the aionotion library.""" with patch("homeassistant.components.notion.config_flow.async_get_client") as mock_: - mock_.return_value = mock_client_coro + mock_.side_effect = mock_client yield mock_ @@ -42,7 +42,7 @@ async def test_duplicate_error(hass): @pytest.mark.parametrize( - "mock_client_coro", [mock_coro(exception=aionotion.errors.NotionError)] + "mock_client", [AsyncMock(side_effect=aionotion.errors.NotionError)] ) async def test_invalid_credentials(hass, mock_aionotion): """Test that an invalid API/App Key throws an error.""" diff --git a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py index 6834c557bd5..a5167104d48 100644 --- a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -3,7 +3,6 @@ import datetime from unittest.mock import ANY from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeed -from asynctest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -37,6 +36,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed CONFIG = { diff --git a/tests/components/nuheat/mocks.py b/tests/components/nuheat/mocks.py index a9adfd3aa57..9755335ccc1 100644 --- a/tests/components/nuheat/mocks.py +++ b/tests/components/nuheat/mocks.py @@ -1,10 +1,11 @@ """The test for the NuHeat thermostat module.""" -from asynctest.mock import MagicMock, Mock from nuheat.config import SCHEDULE_HOLD, SCHEDULE_RUN, SCHEDULE_TEMPORARY_HOLD from homeassistant.components.nuheat.const import DOMAIN from homeassistant.const import CONF_DEVICES, CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import MagicMock, Mock + def _get_mock_thermostat_run(): serial_number = "12345" diff --git a/tests/components/nuheat/test_climate.py b/tests/components/nuheat/test_climate.py index 7bf52026ef9..b407461fa89 100644 --- a/tests/components/nuheat/test_climate.py +++ b/tests/components/nuheat/test_climate.py @@ -1,6 +1,4 @@ """The test for the NuHeat thermostat module.""" -from asynctest.mock import patch - from homeassistant.components.nuheat.const import DOMAIN from homeassistant.setup import async_setup_component @@ -13,6 +11,8 @@ from .mocks import ( _mock_get_config, ) +from tests.async_mock import patch + async def test_climate_thermostat_run(hass): """Test a thermostat with the schedule running.""" diff --git a/tests/components/nuheat/test_config_flow.py b/tests/components/nuheat/test_config_flow.py index 338509f09d1..4c392841142 100644 --- a/tests/components/nuheat/test_config_flow.py +++ b/tests/components/nuheat/test_config_flow.py @@ -1,5 +1,4 @@ """Test the NuHeat config flow.""" -from asynctest import MagicMock, patch import requests from homeassistant import config_entries, setup @@ -8,6 +7,8 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_INTERNAL_SERV from .mocks import _get_mock_thermostat_run +from tests.async_mock import MagicMock, patch + async def test_form_user(hass): """Test we get the form with user source.""" diff --git a/tests/components/nut/test_config_flow.py b/tests/components/nut/test_config_flow.py index ac20c989de9..7eb0ac20184 100644 --- a/tests/components/nut/test_config_flow.py +++ b/tests/components/nut/test_config_flow.py @@ -1,12 +1,11 @@ """Test the Network UPS Tools (NUT) config flow.""" -from asynctest import patch - from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.nut.const import DOMAIN from homeassistant.const import CONF_RESOURCES, CONF_SCAN_INTERVAL from .util import _get_mock_pynutclient +from tests.async_mock import patch from tests.common import MockConfigEntry VALID_CONFIG = { diff --git a/tests/components/nut/util.py b/tests/components/nut/util.py index 788076a7c9f..5622438d70b 100644 --- a/tests/components/nut/util.py +++ b/tests/components/nut/util.py @@ -2,12 +2,11 @@ import json -from asynctest import MagicMock, patch - from homeassistant.components.nut.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_PORT, CONF_RESOURCES from homeassistant.core import HomeAssistant +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry, load_fixture diff --git a/tests/components/nws/conftest.py b/tests/components/nws/conftest.py index 14cee7aa7cb..ac8428ddf48 100644 --- a/tests/components/nws/conftest.py +++ b/tests/components/nws/conftest.py @@ -3,7 +3,7 @@ from unittest.mock import patch import pytest -from tests.common import mock_coro +from tests.async_mock import AsyncMock from tests.components.nws.const import DEFAULT_FORECAST, DEFAULT_OBSERVATION @@ -12,10 +12,10 @@ def mock_simple_nws(): """Mock pynws SimpleNWS with default values.""" with patch("homeassistant.components.nws.SimpleNWS") as mock_nws: instance = mock_nws.return_value - instance.set_station.return_value = mock_coro() - instance.update_observation.return_value = mock_coro() - instance.update_forecast.return_value = mock_coro() - instance.update_forecast_hourly.return_value = mock_coro() + instance.set_station = AsyncMock(return_value=None) + instance.update_observation = AsyncMock(return_value=None) + instance.update_forecast = AsyncMock(return_value=None) + instance.update_forecast_hourly = AsyncMock(return_value=None) instance.station = "ABC" instance.stations = ["ABC"] instance.observation = DEFAULT_OBSERVATION @@ -29,7 +29,7 @@ def mock_simple_nws_config(): """Mock pynws SimpleNWS with default values in config_flow.""" with patch("homeassistant.components.nws.config_flow.SimpleNWS") as mock_nws: instance = mock_nws.return_value - instance.set_station.return_value = mock_coro() + instance.set_station = AsyncMock(return_value=None) instance.station = "ABC" instance.stations = ["ABC"] yield mock_nws diff --git a/tests/components/nws/test_config_flow.py b/tests/components/nws/test_config_flow.py index d4957d4c989..bca852fa379 100644 --- a/tests/components/nws/test_config_flow.py +++ b/tests/components/nws/test_config_flow.py @@ -1,10 +1,11 @@ """Test the National Weather Service (NWS) config flow.""" import aiohttp -from asynctest import patch from homeassistant import config_entries, setup from homeassistant.components.nws.const import DOMAIN +from tests.async_mock import patch + async def test_form(hass, mock_simple_nws_config): """Test we get the form.""" diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index f5a246bcb4d..c164f2f03a2 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -1,19 +1,13 @@ """The tests for the openalpr cloud platform.""" import asyncio -from asynctest import PropertyMock, patch - from homeassistant.components import camera, image_processing as ip from homeassistant.components.openalpr_cloud.image_processing import OPENALPR_API_URL from homeassistant.core import callback from homeassistant.setup import setup_component -from tests.common import ( - assert_setup_component, - get_test_home_assistant, - load_fixture, - mock_coro, -) +from tests.async_mock import PropertyMock, patch +from tests.common import assert_setup_component, get_test_home_assistant, load_fixture from tests.components.image_processing import common @@ -146,7 +140,7 @@ class TestOpenAlprCloud: with patch( "homeassistant.components.camera.async_get_image", - return_value=mock_coro(camera.Image("image/jpeg", b"image")), + return_value=camera.Image("image/jpeg", b"image"), ): common.scan(self.hass, entity_id="image_processing.test_local") self.hass.block_till_done() @@ -179,7 +173,7 @@ class TestOpenAlprCloud: with patch( "homeassistant.components.camera.async_get_image", - return_value=mock_coro(camera.Image("image/jpeg", b"image")), + return_value=camera.Image("image/jpeg", b"image"), ): common.scan(self.hass, entity_id="image_processing.test_local") self.hass.block_till_done() @@ -195,7 +189,7 @@ class TestOpenAlprCloud: with patch( "homeassistant.components.camera.async_get_image", - return_value=mock_coro(camera.Image("image/jpeg", b"image")), + return_value=camera.Image("image/jpeg", b"image"), ): common.scan(self.hass, entity_id="image_processing.test_local") self.hass.block_till_done() diff --git a/tests/components/openalpr_local/test_image_processing.py b/tests/components/openalpr_local/test_image_processing.py index 3f62c906974..996d23184a2 100644 --- a/tests/components/openalpr_local/test_image_processing.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -1,16 +1,15 @@ """The tests for the openalpr local platform.""" -from asynctest import MagicMock, PropertyMock, patch - import homeassistant.components.image_processing as ip from homeassistant.const import ATTR_ENTITY_PICTURE from homeassistant.core import callback from homeassistant.setup import setup_component +from tests.async_mock import MagicMock, PropertyMock, patch from tests.common import assert_setup_component, get_test_home_assistant, load_fixture from tests.components.image_processing import common -async def mock_async_subprocess(): +def mock_async_subprocess(): """Get a Popen mock back.""" async_popen = MagicMock() diff --git a/tests/components/opentherm_gw/test_config_flow.py b/tests/components/opentherm_gw/test_config_flow.py index 2dec360eca0..003d2ad7170 100644 --- a/tests/components/opentherm_gw/test_config_flow.py +++ b/tests/components/opentherm_gw/test_config_flow.py @@ -1,7 +1,6 @@ """Test the Opentherm Gateway config flow.""" import asyncio -from asynctest import patch from pyotgw.vars import OTGW_ABOUT from serial import SerialException @@ -13,7 +12,8 @@ from homeassistant.components.opentherm_gw.const import ( ) from homeassistant.const import CONF_DEVICE, CONF_ID, CONF_NAME, PRECISION_HALVES -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_form_user(hass): @@ -26,16 +26,13 @@ async def test_form_user(hass): assert result["errors"] == {} with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup", return_value=True, ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", - return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}), + "pyotgw.pyotgw.connect", return_value={OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}, ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=mock_coro(None) + "pyotgw.pyotgw.disconnect", return_value=None ) as mock_pyotgw_disconnect: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} @@ -59,16 +56,13 @@ async def test_form_import(hass): """Test import from existing config.""" await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup", return_value=True, ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", - return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}), + "pyotgw.pyotgw.connect", return_value={OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}, ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=mock_coro(None) + "pyotgw.pyotgw.disconnect", return_value=None ) as mock_pyotgw_disconnect: result = await hass.config_entries.flow.async_init( DOMAIN, @@ -102,16 +96,13 @@ async def test_form_duplicate_entries(hass): ) with patch( - "homeassistant.components.opentherm_gw.async_setup", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup", return_value=True, ) as mock_setup, patch( - "homeassistant.components.opentherm_gw.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.opentherm_gw.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "pyotgw.pyotgw.connect", - return_value=mock_coro({OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}), + "pyotgw.pyotgw.connect", return_value={OTGW_ABOUT: "OpenTherm Gateway 4.2.5"}, ) as mock_pyotgw_connect, patch( - "pyotgw.pyotgw.disconnect", return_value=mock_coro(None) + "pyotgw.pyotgw.disconnect", return_value=None ) as mock_pyotgw_disconnect: result1 = await hass.config_entries.flow.async_configure( flow1["flow_id"], {CONF_NAME: "Test Entry 1", CONF_DEVICE: "/dev/ttyUSB0"} diff --git a/tests/components/owntracks/test_config_flow.py b/tests/components/owntracks/test_config_flow.py index 514a559ac1d..676e47523fa 100644 --- a/tests/components/owntracks/test_config_flow.py +++ b/tests/components/owntracks/test_config_flow.py @@ -1,5 +1,4 @@ """Tests for OwnTracks config flow.""" -from asynctest import Mock, patch import pytest from homeassistant import data_entry_flow @@ -9,7 +8,8 @@ from homeassistant.components.owntracks.const import DOMAIN from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import Mock, patch +from tests.common import MockConfigEntry CONF_WEBHOOK_URL = "webhook_url" @@ -140,7 +140,7 @@ async def test_unload(hass): with patch( "homeassistant.config_entries.ConfigEntries.async_forward_entry_unload", - return_value=mock_coro(), + return_value=None, ) as mock_unload: assert await hass.config_entries.async_unload(entry.entry_id) @@ -157,7 +157,7 @@ async def test_with_cloud_sub(hass): "homeassistant.components.cloud.async_active_subscription", return_value=True ), patch( "homeassistant.components.cloud.async_create_cloudhook", - return_value=mock_coro("https://hooks.nabu.casa/ABCD"), + return_value="https://hooks.nabu.casa/ABCD", ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": "user"}, data={} diff --git a/tests/components/owntracks/test_device_tracker.py b/tests/components/owntracks/test_device_tracker.py index bdd1199008c..d71f0fe0aee 100644 --- a/tests/components/owntracks/test_device_tracker.py +++ b/tests/components/owntracks/test_device_tracker.py @@ -1,13 +1,13 @@ """The tests for the Owntracks device tracker.""" import json -from asynctest import patch import pytest from homeassistant.components import owntracks from homeassistant.const import STATE_NOT_HOME from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_fire_mqtt_message, diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index fd2738508ea..cc7c3f58e82 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -1,7 +1,6 @@ """Test the Panasonic Viera config flow.""" from unittest.mock import Mock -from asynctest import patch from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED, SOAPError import pytest @@ -20,6 +19,7 @@ from homeassistant.components.panasonic_viera.const import ( ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 76350619983..887f0d94fef 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -1,7 +1,6 @@ """The tests for the person component.""" import logging -from asynctest import patch import pytest from homeassistant.components import person @@ -24,6 +23,7 @@ from homeassistant.core import Context, CoreState, State from homeassistant.helpers import collection, entity_registry from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, mock_component, mock_restore_cache DEVICE_TRACKER = "device_tracker.test_tracker" diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index c2d9ec77f03..236e8eadde8 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -2,10 +2,9 @@ from unittest.mock import patch -from asynctest import CoroutineMock - from homeassistant.components import pi_hole +from tests.async_mock import AsyncMock from tests.common import async_setup_component ZERO_DATA = { @@ -25,7 +24,7 @@ ZERO_DATA = { async def test_setup_minimal_config(hass): """Tests component setup with minimal config.""" with patch("homeassistant.components.pi_hole.Hole") as _hole: - _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.get_data = AsyncMock(return_value=None) _hole.return_value.data = ZERO_DATA assert await async_setup_component( @@ -82,7 +81,7 @@ async def test_setup_minimal_config(hass): async def test_setup_name_config(hass): """Tests component setup with a custom name.""" with patch("homeassistant.components.pi_hole.Hole") as _hole: - _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.get_data = AsyncMock(return_value=None) _hole.return_value.data = ZERO_DATA assert await async_setup_component( @@ -102,9 +101,9 @@ async def test_setup_name_config(hass): async def test_disable_service_call(hass): """Test disable service call with no Pi-hole named.""" with patch("homeassistant.components.pi_hole.Hole") as _hole: - mock_disable = CoroutineMock(return_value=None) + mock_disable = AsyncMock(return_value=None) _hole.return_value.disable = mock_disable - _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.get_data = AsyncMock(return_value=None) _hole.return_value.data = ZERO_DATA assert await async_setup_component( @@ -135,9 +134,9 @@ async def test_disable_service_call(hass): async def test_enable_service_call(hass): """Test enable service call with no Pi-hole named.""" with patch("homeassistant.components.pi_hole.Hole") as _hole: - mock_enable = CoroutineMock(return_value=None) + mock_enable = AsyncMock(return_value=None) _hole.return_value.enable = mock_enable - _hole.return_value.get_data = CoroutineMock(return_value=None) + _hole.return_value.get_data = AsyncMock(return_value=None) _hole.return_value.data = ZERO_DATA assert await async_setup_component( diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index d839ccc674b..af065b55e50 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -1,7 +1,6 @@ """Tests for Plex config flow.""" import copy -from asynctest import patch import plexapi.exceptions import requests.exceptions @@ -26,6 +25,7 @@ from homeassistant.setup import async_setup_component from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN from .mock_classes import MockPlexAccount, MockPlexServer +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index 09a2e8f6ada..e34476f1813 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -3,9 +3,7 @@ import copy from datetime import timedelta import ssl -from asynctest import ClockedTestCase, patch import plexapi -import pytest import requests from homeassistant.components.media_player import DOMAIN as MP_DOMAIN @@ -31,11 +29,8 @@ import homeassistant.util.dt as dt_util from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN from .mock_classes import MockPlexAccount, MockPlexServer -from tests.common import ( - MockConfigEntry, - async_fire_time_changed, - async_test_home_assistant, -) +from tests.async_mock import patch +from tests.common import MockConfigEntry, async_fire_time_changed async def test_setup_with_config(hass): @@ -73,87 +68,86 @@ async def test_setup_with_config(hass): assert loaded_server.plex_server == mock_plex_server -@pytest.mark.skip -class TestClockedPlex(ClockedTestCase): - """Create clock-controlled asynctest class.""" +# class TestClockedPlex(ClockedTestCase): +# """Create clock-controlled tests.async_mock class.""" - @pytest.fixture(autouse=True) - def inject_fixture(self, caplog, hass_storage): - """Inject pytest fixtures as instance attributes.""" - self.caplog = caplog +# @pytest.fixture(autouse=True) +# def inject_fixture(self, caplog, hass_storage): +# """Inject pytest fixtures as instance attributes.""" +# self.caplog = caplog - async def setUp(self): - """Initialize this test class.""" - self.hass = await async_test_home_assistant(self.loop) +# async def setUp(self): +# """Initialize this test class.""" +# self.hass = await async_test_home_assistant(self.loop) - async def tearDown(self): - """Clean up the HomeAssistant instance.""" - await self.hass.async_stop() +# async def tearDown(self): +# """Clean up the HomeAssistant instance.""" +# await self.hass.async_stop() - async def test_setup_with_config_entry(self): - """Test setup component with config.""" - hass = self.hass +# async def test_setup_with_config_entry(self): +# """Test setup component with config.""" +# hass = self.hass - mock_plex_server = MockPlexServer() +# mock_plex_server = MockPlexServer() - entry = MockConfigEntry( - domain=const.DOMAIN, - data=DEFAULT_DATA, - options=DEFAULT_OPTIONS, - unique_id=DEFAULT_DATA["server_id"], - ) +# entry = MockConfigEntry( +# domain=const.DOMAIN, +# data=DEFAULT_DATA, +# options=DEFAULT_OPTIONS, +# unique_id=DEFAULT_DATA["server_id"], +# ) - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() +# with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( +# "homeassistant.components.plex.PlexWebsocket.listen" +# ) as mock_listen: +# entry.add_to_hass(hass) +# assert await hass.config_entries.async_setup(entry.entry_id) +# await hass.async_block_till_done() - assert mock_listen.called +# assert mock_listen.called - assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 - assert entry.state == ENTRY_STATE_LOADED +# assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 +# assert entry.state == ENTRY_STATE_LOADED - server_id = mock_plex_server.machineIdentifier - loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] +# server_id = mock_plex_server.machineIdentifier +# loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] - assert loaded_server.plex_server == mock_plex_server +# assert loaded_server.plex_server == mock_plex_server - async_dispatcher_send( - hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) - ) - await hass.async_block_till_done() +# async_dispatcher_send( +# hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) +# ) +# await hass.async_block_till_done() - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) +# sensor = hass.states.get("sensor.plex_plex_server_1") +# assert sensor.state == str(len(mock_plex_server.accounts)) - # Ensure existing entities refresh - await self.advance(const.DEBOUNCE_TIMEOUT) - async_dispatcher_send( - hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) - ) - await hass.async_block_till_done() +# # Ensure existing entities refresh +# await self.advance(const.DEBOUNCE_TIMEOUT) +# async_dispatcher_send( +# hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) +# ) +# await hass.async_block_till_done() - for test_exception in ( - plexapi.exceptions.BadRequest, - requests.exceptions.RequestException, - ): - with patch.object( - mock_plex_server, "clients", side_effect=test_exception - ) as patched_clients_bad_request: - await self.advance(const.DEBOUNCE_TIMEOUT) - async_dispatcher_send( - hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) - ) - await hass.async_block_till_done() +# for test_exception in ( +# plexapi.exceptions.BadRequest, +# requests.exceptions.RequestException, +# ): +# with patch.object( +# mock_plex_server, "clients", side_effect=test_exception +# ) as patched_clients_bad_request: +# await self.advance(const.DEBOUNCE_TIMEOUT) +# async_dispatcher_send( +# hass, const.PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id) +# ) +# await hass.async_block_till_done() - assert patched_clients_bad_request.called - assert ( - f"Could not connect to Plex server: {mock_plex_server.friendlyName}" - in self.caplog.text - ) - self.caplog.clear() +# assert patched_clients_bad_request.called +# assert ( +# f"Could not connect to Plex server: {mock_plex_server.friendlyName}" +# in self.caplog.text +# ) +# self.caplog.clear() async def test_set_config_entry_unique_id(hass): diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index b3cbd6c79d4..7cb34b4fcca 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -1,14 +1,10 @@ """Tests for Plex server.""" import copy -from asynctest import ClockedTestCase, patch -import pytest - from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.plex.const import ( CONF_IGNORE_NEW_SHARED_USERS, CONF_MONITORED_USERS, - DEBOUNCE_TIMEOUT, DOMAIN, PLEX_UPDATE_PLATFORMS_SIGNAL, SERVERS, @@ -18,7 +14,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DEFAULT_DATA, DEFAULT_OPTIONS from .mock_classes import MockPlexServer -from tests.common import MockConfigEntry, async_test_home_assistant +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_new_users_available(hass): @@ -108,107 +105,106 @@ async def test_new_ignored_users_available(hass, caplog): assert sensor.state == str(len(mock_plex_server.accounts)) -@pytest.mark.skip -class TestClockedPlex(ClockedTestCase): - """Create clock-controlled asynctest class.""" +# class TestClockedPlex(ClockedTestCase): +# """Create clock-controlled tests.async_mock class.""" - async def setUp(self): - """Initialize this test class.""" - self.hass = await async_test_home_assistant(self.loop) +# async def setUp(self): +# """Initialize this test class.""" +# self.hass = await async_test_home_assistant(self.loop) - async def tearDown(self): - """Clean up the HomeAssistant instance.""" - await self.hass.async_stop() +# async def tearDown(self): +# """Clean up the HomeAssistant instance.""" +# await self.hass.async_stop() - async def test_mark_sessions_idle(self): - """Test marking media_players as idle when sessions end.""" - hass = self.hass +# async def test_mark_sessions_idle(self): +# """Test marking media_players as idle when sessions end.""" +# hass = self.hass - entry = MockConfigEntry( - domain=DOMAIN, - data=DEFAULT_DATA, - options=DEFAULT_OPTIONS, - unique_id=DEFAULT_DATA["server_id"], - ) +# entry = MockConfigEntry( +# domain=DOMAIN, +# data=DEFAULT_DATA, +# options=DEFAULT_OPTIONS, +# unique_id=DEFAULT_DATA["server_id"], +# ) - mock_plex_server = MockPlexServer(config_entry=entry) +# mock_plex_server = MockPlexServer(config_entry=entry) - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() +# with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( +# "homeassistant.components.plex.PlexWebsocket.listen" +# ): +# entry.add_to_hass(hass) +# assert await hass.config_entries.async_setup(entry.entry_id) +# await hass.async_block_till_done() - server_id = mock_plex_server.machineIdentifier +# server_id = mock_plex_server.machineIdentifier - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == str(len(mock_plex_server.accounts)) +# sensor = hass.states.get("sensor.plex_plex_server_1") +# assert sensor.state == str(len(mock_plex_server.accounts)) - mock_plex_server.clear_clients() - mock_plex_server.clear_sessions() +# mock_plex_server.clear_clients() +# mock_plex_server.clear_sessions() - await self.advance(DEBOUNCE_TIMEOUT) - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() +# await self.advance(DEBOUNCE_TIMEOUT) +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() - sensor = hass.states.get("sensor.plex_plex_server_1") - assert sensor.state == "0" +# sensor = hass.states.get("sensor.plex_plex_server_1") +# assert sensor.state == "0" - async def test_debouncer(self): - """Test debouncer behavior.""" - hass = self.hass +# async def test_debouncer(self): +# """Test debouncer behavior.""" +# hass = self.hass - entry = MockConfigEntry( - domain=DOMAIN, - data=DEFAULT_DATA, - options=DEFAULT_OPTIONS, - unique_id=DEFAULT_DATA["server_id"], - ) +# entry = MockConfigEntry( +# domain=DOMAIN, +# data=DEFAULT_DATA, +# options=DEFAULT_OPTIONS, +# unique_id=DEFAULT_DATA["server_id"], +# ) - mock_plex_server = MockPlexServer(config_entry=entry) +# mock_plex_server = MockPlexServer(config_entry=entry) - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ): - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() +# with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( +# "homeassistant.components.plex.PlexWebsocket.listen" +# ): +# entry.add_to_hass(hass) +# assert await hass.config_entries.async_setup(entry.entry_id) +# await hass.async_block_till_done() - server_id = mock_plex_server.machineIdentifier +# server_id = mock_plex_server.machineIdentifier - with patch.object(mock_plex_server, "clients", return_value=[]), patch.object( - mock_plex_server, "sessions", return_value=[] - ) as mock_update: - # Called immediately - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() - assert mock_update.call_count == 1 +# with patch.object(mock_plex_server, "clients", return_value=[]), patch.object( +# mock_plex_server, "sessions", return_value=[] +# ) as mock_update: +# # Called immediately +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() +# assert mock_update.call_count == 1 - # Throttled - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() - assert mock_update.call_count == 1 +# # Throttled +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() +# assert mock_update.call_count == 1 - # Throttled - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() - assert mock_update.call_count == 1 +# # Throttled +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() +# assert mock_update.call_count == 1 - # Called from scheduler - await self.advance(DEBOUNCE_TIMEOUT) - await hass.async_block_till_done() - assert mock_update.call_count == 2 +# # Called from scheduler +# await self.advance(DEBOUNCE_TIMEOUT) +# await hass.async_block_till_done() +# assert mock_update.call_count == 2 - # Throttled - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) - await hass.async_block_till_done() - assert mock_update.call_count == 2 +# # Throttled +# async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) +# await hass.async_block_till_done() +# assert mock_update.call_count == 2 - # Called from scheduler - await self.advance(DEBOUNCE_TIMEOUT) - await hass.async_block_till_done() - assert mock_update.call_count == 3 +# # Called from scheduler +# await self.advance(DEBOUNCE_TIMEOUT) +# await hass.async_block_till_done() +# assert mock_update.call_count == 3 diff --git a/tests/components/point/test_config_flow.py b/tests/components/point/test_config_flow.py index c1c705e752d..1714dd5a352 100644 --- a/tests/components/point/test_config_flow.py +++ b/tests/components/point/test_config_flow.py @@ -1,21 +1,20 @@ """Tests for the Point config flow.""" import asyncio -from unittest.mock import Mock, patch import pytest from homeassistant import data_entry_flow from homeassistant.components.point import DOMAIN, config_flow -from tests.common import mock_coro +from tests.async_mock import AsyncMock, patch def init_config_flow(hass, side_effect=None): """Init a configuration flow.""" config_flow.register_flow_implementation(hass, DOMAIN, "id", "secret") flow = config_flow.PointFlowHandler() - flow._get_authorization_url = Mock( # pylint: disable=protected-access - return_value=mock_coro("https://example.com"), side_effect=side_effect + flow._get_authorization_url = AsyncMock( # pylint: disable=protected-access + return_value="https://example.com", side_effect=side_effect ) flow.hass = hass return flow diff --git a/tests/components/powerwall/mocks.py b/tests/components/powerwall/mocks.py index 8a559d1dd49..a384725daa8 100644 --- a/tests/components/powerwall/mocks.py +++ b/tests/components/powerwall/mocks.py @@ -3,7 +3,6 @@ import json import os -from asynctest import MagicMock, Mock from tesla_powerwall import ( DeviceType, GridStatus, @@ -17,6 +16,7 @@ from tesla_powerwall import ( from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import CONF_IP_ADDRESS +from tests.async_mock import MagicMock, Mock from tests.common import load_fixture diff --git a/tests/components/powerwall/test_binary_sensor.py b/tests/components/powerwall/test_binary_sensor.py index c8a081de573..fcca7fb34ab 100644 --- a/tests/components/powerwall/test_binary_sensor.py +++ b/tests/components/powerwall/test_binary_sensor.py @@ -1,13 +1,13 @@ """The binary sensor tests for the powerwall platform.""" -from asynctest import patch - from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import STATE_ON from homeassistant.setup import async_setup_component from .mocks import _mock_get_config, _mock_powerwall_with_fixtures +from tests.async_mock import patch + async def test_sensors(hass): """Test creation of the binary sensors.""" diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index 097346c5ac7..eaf53f0beef 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -1,6 +1,5 @@ """Test the Powerwall config flow.""" -from asynctest import patch from tesla_powerwall import APIChangedError, PowerwallUnreachableError from homeassistant import config_entries, setup @@ -9,6 +8,8 @@ from homeassistant.const import CONF_IP_ADDRESS from .mocks import _mock_powerwall_side_effect, _mock_powerwall_site_name +from tests.async_mock import patch + async def test_form_source_user(hass): """Test we get config flow setup form as a user.""" diff --git a/tests/components/powerwall/test_sensor.py b/tests/components/powerwall/test_sensor.py index c68d9f0279e..af2835ea679 100644 --- a/tests/components/powerwall/test_sensor.py +++ b/tests/components/powerwall/test_sensor.py @@ -1,13 +1,13 @@ """The sensor tests for the powerwall platform.""" -from asynctest import patch - from homeassistant.components.powerwall.const import DOMAIN from homeassistant.const import UNIT_PERCENTAGE from homeassistant.setup import async_setup_component from .mocks import _mock_get_config, _mock_powerwall_with_fixtures +from tests.async_mock import patch + async def test_sensors(hass): """Test creation of the sensors.""" diff --git a/tests/components/ps4/conftest.py b/tests/components/ps4/conftest.py index e945af3220d..42002404db2 100644 --- a/tests/components/ps4/conftest.py +++ b/tests/components/ps4/conftest.py @@ -1,7 +1,8 @@ """Test configuration for PS4.""" -from asynctest import patch import pytest +from tests.async_mock import patch + @pytest.fixture def patch_load_json(): diff --git a/tests/components/ps4/test_config_flow.py b/tests/components/ps4/test_config_flow.py index 2bad2d1e281..c5e0623de5b 100644 --- a/tests/components/ps4/test_config_flow.py +++ b/tests/components/ps4/test_config_flow.py @@ -1,5 +1,4 @@ """Define tests for the PlayStation 4 config flow.""" -from asynctest import patch from pyps4_2ndscreen.errors import CredentialTimeout import pytest @@ -21,6 +20,7 @@ from homeassistant.const import ( ) from homeassistant.util import location +from tests.async_mock import patch from tests.common import MockConfigEntry MOCK_TITLE = "PlayStation 4" diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 68179ad54c2..7f7eff33ebb 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -1,6 +1,4 @@ """Tests for the PS4 Integration.""" -from asynctest import MagicMock, patch - from homeassistant import config_entries, data_entry_flow from homeassistant.components import ps4 from homeassistant.components.media_player.const import ( @@ -29,6 +27,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component from homeassistant.util import location +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry, mock_registry MOCK_HOST = "192.168.0.1" diff --git a/tests/components/ps4/test_media_player.py b/tests/components/ps4/test_media_player.py index b7725795f5c..8ff8dea71ce 100644 --- a/tests/components/ps4/test_media_player.py +++ b/tests/components/ps4/test_media_player.py @@ -1,5 +1,4 @@ """Tests for the PS4 media player platform.""" -from asynctest import MagicMock, patch from pyps4_2ndscreen.credential import get_ddp_message from homeassistant.components import ps4 @@ -34,6 +33,7 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry, mock_device_registry, mock_registry MOCK_CREDS = "123412341234abcd12341234abcd12341234abcd12341234abcd12341234abcd" diff --git a/tests/components/ptvsd/test_ptvsd.py b/tests/components/ptvsd/test_ptvsd.py index d4a2aa1ab94..9df686cfcbd 100644 --- a/tests/components/ptvsd/test_ptvsd.py +++ b/tests/components/ptvsd/test_ptvsd.py @@ -2,13 +2,14 @@ from unittest.mock import patch -from asynctest import CoroutineMock from pytest import mark from homeassistant.bootstrap import _async_set_up_integrations import homeassistant.components.ptvsd as ptvsd_component from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock + @mark.skip("causes code cover to fail") async def test_ptvsd(hass): @@ -42,9 +43,7 @@ async def test_ptvsd_bootstrap(hass): """Test loading ptvsd component with wait.""" config = {ptvsd_component.DOMAIN: {ptvsd_component.CONF_WAIT: True}} - with patch( - "homeassistant.components.ptvsd.async_setup", CoroutineMock() - ) as setup_mock: + with patch("homeassistant.components.ptvsd.async_setup", AsyncMock()) as setup_mock: setup_mock.return_value = True await _async_set_up_integrations(hass, config) diff --git a/tests/components/qwikswitch/test_init.py b/tests/components/qwikswitch/test_init.py index b0be2dbc81c..6075174fa98 100644 --- a/tests/components/qwikswitch/test_init.py +++ b/tests/components/qwikswitch/test_init.py @@ -3,13 +3,13 @@ import asyncio import logging from aiohttp.client_exceptions import ClientError -from asynctest import Mock import pytest from yarl import URL from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH from homeassistant.setup import async_setup_component +from tests.async_mock import Mock from tests.test_util.aiohttp import AiohttpClientMockResponse, MockLongPollSideEffect _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/rachio/test_config_flow.py b/tests/components/rachio/test_config_flow.py index cadc346e403..7cc3f272e7a 100644 --- a/tests/components/rachio/test_config_flow.py +++ b/tests/components/rachio/test_config_flow.py @@ -1,7 +1,4 @@ """Test the Rachio config flow.""" -from asynctest import patch -from asynctest.mock import MagicMock - from homeassistant import config_entries, setup from homeassistant.components.rachio.const import ( CONF_CUSTOM_URL, @@ -10,6 +7,7 @@ from homeassistant.components.rachio.const import ( ) from homeassistant.const import CONF_API_KEY +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry diff --git a/tests/components/rainmachine/test_config_flow.py b/tests/components/rainmachine/test_config_flow.py index 4351470cff5..04dc67bdbe8 100644 --- a/tests/components/rainmachine/test_config_flow.py +++ b/tests/components/rainmachine/test_config_flow.py @@ -1,5 +1,4 @@ """Define tests for the OpenUV config flow.""" -from asynctest import patch from regenmaschine.errors import RainMachineError from homeassistant import data_entry_flow @@ -13,7 +12,8 @@ from homeassistant.const import ( CONF_SSL, ) -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_duplicate_error(hass): @@ -51,7 +51,7 @@ async def test_invalid_password(hass): with patch( "homeassistant.components.rainmachine.config_flow.login", - return_value=mock_coro(exception=RainMachineError), + side_effect=RainMachineError, ): result = await flow.async_step_user(user_input=conf) assert result["errors"] == {CONF_PASSWORD: "invalid_credentials"} @@ -84,8 +84,7 @@ async def test_step_import(hass): flow.context = {"source": SOURCE_USER} with patch( - "homeassistant.components.rainmachine.config_flow.login", - return_value=mock_coro(True), + "homeassistant.components.rainmachine.config_flow.login", return_value=True, ): result = await flow.async_step_import(import_config=conf) @@ -116,8 +115,7 @@ async def test_step_user(hass): flow.context = {"source": SOURCE_USER} with patch( - "homeassistant.components.rainmachine.config_flow.login", - return_value=mock_coro(True), + "homeassistant.components.rainmachine.config_flow.login", return_value=True, ): result = await flow.async_step_user(user_input=conf) diff --git a/tests/components/ring/test_config_flow.py b/tests/components/ring/test_config_flow.py index 421e8a26694..57723d1ede7 100644 --- a/tests/components/ring/test_config_flow.py +++ b/tests/components/ring/test_config_flow.py @@ -1,11 +1,9 @@ """Test the Ring config flow.""" -from asynctest import Mock, patch - from homeassistant import config_entries, setup from homeassistant.components.ring import DOMAIN from homeassistant.components.ring.config_flow import InvalidAuth -from tests.common import mock_coro +from tests.async_mock import Mock, patch async def test_form(hass): @@ -23,9 +21,9 @@ async def test_form(hass): fetch_token=Mock(return_value={"access_token": "mock-token"}) ), ), patch( - "homeassistant.components.ring.async_setup", return_value=mock_coro(True) + "homeassistant.components.ring.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.ring.async_setup_entry", return_value=mock_coro(True), + "homeassistant.components.ring.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index 58c9192de7d..ece4142f8c7 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -1,10 +1,10 @@ """The tests for the rmvtransport platform.""" import datetime -from asynctest import patch - from homeassistant.setup import async_setup_component +from tests.async_mock import patch + VALID_CONFIG_MINIMAL = { "sensor": {"platform": "rmvtransport", "next_departure": [{"station": "3000010"}]} } diff --git a/tests/components/roku/test_config_flow.py b/tests/components/roku/test_config_flow.py index e6c725e9959..f59b36b6f30 100644 --- a/tests/components/roku/test_config_flow.py +++ b/tests/components/roku/test_config_flow.py @@ -1,7 +1,6 @@ """Test the Roku config flow.""" from socket import gaierror as SocketGIAError -from asynctest import patch from requests.exceptions import RequestException from requests_mock import Mocker from roku import RokuException @@ -22,6 +21,7 @@ from homeassistant.data_entry_flow import ( from homeassistant.helpers.typing import HomeAssistantType from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.components.roku import ( HOST, SSDP_LOCATION, diff --git a/tests/components/roku/test_init.py b/tests/components/roku/test_init.py index c597ebef6f7..fcbe9aa4b7d 100644 --- a/tests/components/roku/test_init.py +++ b/tests/components/roku/test_init.py @@ -1,7 +1,6 @@ """Tests for the Roku integration.""" from socket import gaierror as SocketGIAError -from asynctest import patch from requests.exceptions import RequestException from requests_mock import Mocker from roku import RokuException @@ -14,6 +13,7 @@ from homeassistant.config_entries import ( ) from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import patch from tests.components.roku import setup_integration diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 4965207a5b8..3b11844450e 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -1,7 +1,6 @@ """Tests for the Roku Media Player platform.""" from datetime import timedelta -from asynctest import PropertyMock, patch from requests.exceptions import ( ConnectionError as RequestsConnectionError, ReadTimeout as RequestsReadTimeout, @@ -46,6 +45,7 @@ from homeassistant.const import ( from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import dt as dt_util +from tests.async_mock import PropertyMock, patch from tests.common import async_fire_time_changed from tests.components.roku import UPNP_SERIAL, setup_integration diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index c9a0d8fde17..0941586f788 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -1,5 +1,4 @@ """Test the iRobot Roomba config flow.""" -from asynctest import MagicMock, PropertyMock, patch from roomba import RoombaConnectionError from homeassistant import config_entries, data_entry_flow, setup @@ -12,6 +11,7 @@ from homeassistant.components.roomba.const import ( ) from homeassistant.const import CONF_HOST, CONF_PASSWORD +from tests.async_mock import MagicMock, PropertyMock, patch from tests.common import MockConfigEntry VALID_CONFIG = {CONF_HOST: "1.2.3.4", CONF_BLID: "blid", CONF_PASSWORD: "password"} diff --git a/tests/components/samsungtv/test_config_flow.py b/tests/components/samsungtv/test_config_flow.py index 4e5a02588b1..2673ee56559 100644 --- a/tests/components/samsungtv/test_config_flow.py +++ b/tests/components/samsungtv/test_config_flow.py @@ -1,7 +1,4 @@ """Tests for Samsung TV config flow.""" -from unittest.mock import Mock, PropertyMock, call, patch - -from asynctest import mock import pytest from samsungctl.exceptions import AccessDenied, UnhandledResponse from samsungtvws.exceptions import ConnectionFailure @@ -21,6 +18,8 @@ from homeassistant.components.ssdp import ( ) from homeassistant.const import CONF_HOST, CONF_ID, CONF_METHOD, CONF_NAME, CONF_TOKEN +from tests.async_mock import DEFAULT as DEFAULT_MOCK, Mock, PropertyMock, call, patch + MOCK_USER_DATA = {CONF_HOST: "fake_host", CONF_NAME: "fake_name"} MOCK_SSDP_DATA = { ATTR_SSDP_LOCATION: "https://fake_host:12345/test", @@ -70,11 +69,11 @@ def remote_fixture(): ) as remote_class, patch( "homeassistant.components.samsungtv.config_flow.socket" ) as socket_class: - remote = mock.Mock() - remote.__enter__ = mock.Mock() - remote.__exit__ = mock.Mock() + remote = Mock() + remote.__enter__ = Mock() + remote.__exit__ = Mock() remote_class.return_value = remote - socket = mock.Mock() + socket = Mock() socket_class.return_value = socket socket_class.gethostbyname.return_value = "FAKE_IP_ADDRESS" yield remote @@ -88,12 +87,12 @@ def remotews_fixture(): ) as remotews_class, patch( "homeassistant.components.samsungtv.config_flow.socket" ) as socket_class: - remotews = mock.Mock() - remotews.__enter__ = mock.Mock() - remotews.__exit__ = mock.Mock() + remotews = Mock() + remotews.__enter__ = Mock() + remotews.__exit__ = Mock() remotews_class.return_value = remotews remotews_class().__enter__().token = "FAKE_TOKEN" - socket = mock.Mock() + socket = Mock() socket_class.return_value = socket socket_class.gethostbyname.return_value = "FAKE_IP_ADDRESS" yield remotews @@ -486,7 +485,7 @@ async def test_autodetect_websocket_ssl(hass, remote, remotews): "homeassistant.components.samsungtv.bridge.Remote", side_effect=OSError("Boom"), ), patch( "homeassistant.components.samsungtv.bridge.SamsungTVWS", - side_effect=[WebSocketProtocolException("Boom"), mock.DEFAULT], + side_effect=[WebSocketProtocolException("Boom"), DEFAULT_MOCK], ) as remotews: enter = Mock() type(enter).token = PropertyMock(return_value="123456789") diff --git a/tests/components/samsungtv/test_init.py b/tests/components/samsungtv/test_init.py index 232a04416d5..5ef47cb3106 100644 --- a/tests/components/samsungtv/test_init.py +++ b/tests/components/samsungtv/test_init.py @@ -1,6 +1,4 @@ """Tests for the Samsung TV Integration.""" -from asynctest import mock -from asynctest.mock import call, patch import pytest from homeassistant.components.media_player.const import DOMAIN, SUPPORT_TURN_ON @@ -18,6 +16,8 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, call, patch + ENTITY_ID = f"{DOMAIN}.fake_name" MOCK_CONFIG = { SAMSUNGTV_DOMAIN: [ @@ -49,9 +49,9 @@ def remote_fixture(): ) as socket1, patch( "homeassistant.components.samsungtv.socket" ) as socket2: - remote = mock.Mock() - remote.__enter__ = mock.Mock() - remote.__exit__ = mock.Mock() + remote = Mock() + remote.__enter__ = Mock() + remote.__exit__ = Mock() remote_class.return_value = remote socket1.gethostbyname.return_value = "FAKE_IP_ADDRESS" socket2.gethostbyname.return_value = "FAKE_IP_ADDRESS" diff --git a/tests/components/samsungtv/test_media_player.py b/tests/components/samsungtv/test_media_player.py index c549e57a06e..15ac13c64d5 100644 --- a/tests/components/samsungtv/test_media_player.py +++ b/tests/components/samsungtv/test_media_player.py @@ -3,8 +3,6 @@ import asyncio from datetime import timedelta import logging -from asynctest import mock -from asynctest.mock import call, patch import pytest from samsungctl import exceptions from samsungtvws.exceptions import ConnectionFailure @@ -54,6 +52,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import DEFAULT as DEFAULT_MOCK, Mock, PropertyMock, call, patch from tests.common import MockConfigEntry, async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake" @@ -120,9 +119,9 @@ def remote_fixture(): ) as socket1, patch( "homeassistant.components.samsungtv.socket" ) as socket2: - remote = mock.Mock() - remote.__enter__ = mock.Mock() - remote.__exit__ = mock.Mock() + remote = Mock() + remote.__enter__ = Mock() + remote.__exit__ = Mock() remote_class.return_value = remote socket1.gethostbyname.return_value = "FAKE_IP_ADDRESS" socket2.gethostbyname.return_value = "FAKE_IP_ADDRESS" @@ -139,9 +138,9 @@ def remotews_fixture(): ) as socket1, patch( "homeassistant.components.samsungtv.socket" ) as socket2: - remote = mock.Mock() - remote.__enter__ = mock.Mock() - remote.__exit__ = mock.Mock() + remote = Mock() + remote.__enter__ = Mock() + remote.__exit__ = Mock() remote_class.return_value = remote remote_class().__enter__().token = "FAKE_TOKEN" socket1.gethostbyname.return_value = "FAKE_IP_ADDRESS" @@ -185,11 +184,11 @@ async def test_setup_without_turnon(hass, remote): async def test_setup_websocket(hass, remotews, mock_now): """Test setup of platform.""" with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: - enter = mock.Mock() - type(enter).token = mock.PropertyMock(return_value="987654321") - remote = mock.Mock() - remote.__enter__ = mock.Mock(return_value=enter) - remote.__exit__ = mock.Mock() + enter = Mock() + type(enter).token = PropertyMock(return_value="987654321") + remote = Mock() + remote.__enter__ = Mock(return_value=enter) + remote.__exit__ = Mock() remote_class.return_value = remote await setup_samsungtv(hass, MOCK_CONFIGWS) @@ -247,7 +246,7 @@ async def test_update_off(hass, remote, mock_now): with patch( "homeassistant.components.samsungtv.bridge.Remote", - side_effect=[OSError("Boom"), mock.DEFAULT], + side_effect=[OSError("Boom"), DEFAULT_MOCK], ): next_update = mock_now + timedelta(minutes=5) @@ -283,7 +282,7 @@ async def test_update_connection_failure(hass, remotews, mock_now): """Testing update tv connection failure exception.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", - side_effect=[OSError("Boom"), mock.DEFAULT], + side_effect=[OSError("Boom"), DEFAULT_MOCK], ): await setup_samsungtv(hass, MOCK_CONFIGWS) @@ -309,7 +308,7 @@ async def test_update_unhandled_response(hass, remote, mock_now): with patch( "homeassistant.components.samsungtv.bridge.Remote", - side_effect=[exceptions.UnhandledResponse("Boom"), mock.DEFAULT], + side_effect=[exceptions.UnhandledResponse("Boom"), DEFAULT_MOCK], ): next_update = mock_now + timedelta(minutes=5) @@ -339,7 +338,7 @@ async def test_send_key(hass, remote): async def test_send_key_broken_pipe(hass, remote): """Testing broken pipe Exception.""" await setup_samsungtv(hass, MOCK_CONFIG) - remote.control = mock.Mock(side_effect=BrokenPipeError("Boom")) + remote.control = Mock(side_effect=BrokenPipeError("Boom")) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True ) @@ -350,8 +349,8 @@ async def test_send_key_broken_pipe(hass, remote): async def test_send_key_connection_closed_retry_succeed(hass, remote): """Test retry on connection closed.""" await setup_samsungtv(hass, MOCK_CONFIG) - remote.control = mock.Mock( - side_effect=[exceptions.ConnectionClosed("Boom"), mock.DEFAULT, mock.DEFAULT] + remote.control = Mock( + side_effect=[exceptions.ConnectionClosed("Boom"), DEFAULT_MOCK, DEFAULT_MOCK] ) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True @@ -371,7 +370,7 @@ async def test_send_key_connection_closed_retry_succeed(hass, remote): async def test_send_key_unhandled_response(hass, remote): """Testing unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) - remote.control = mock.Mock(side_effect=exceptions.UnhandledResponse("Boom")) + remote.control = Mock(side_effect=exceptions.UnhandledResponse("Boom")) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True ) @@ -382,7 +381,7 @@ async def test_send_key_unhandled_response(hass, remote): async def test_send_key_websocketexception(hass, remote): """Testing unhandled response exception.""" await setup_samsungtv(hass, MOCK_CONFIG) - remote.control = mock.Mock(side_effect=WebSocketException("Boom")) + remote.control = Mock(side_effect=WebSocketException("Boom")) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True ) @@ -393,7 +392,7 @@ async def test_send_key_websocketexception(hass, remote): async def test_send_key_os_error(hass, remote): """Testing broken pipe Exception.""" await setup_samsungtv(hass, MOCK_CONFIG) - remote.control = mock.Mock(side_effect=OSError("Boom")) + remote.control = Mock(side_effect=OSError("Boom")) assert await hass.services.async_call( DOMAIN, SERVICE_VOLUME_UP, {ATTR_ENTITY_ID: ENTITY_ID}, True ) @@ -467,7 +466,7 @@ async def test_turn_off_websocket(hass, remotews): """Test for turn_off.""" with patch( "homeassistant.components.samsungtv.bridge.Remote", - side_effect=[OSError("Boom"), mock.DEFAULT], + side_effect=[OSError("Boom"), DEFAULT_MOCK], ): await setup_samsungtv(hass, MOCK_CONFIGWS) assert await hass.services.async_call( @@ -493,7 +492,7 @@ async def test_turn_off_os_error(hass, remote, caplog): """Test for turn_off with OSError.""" caplog.set_level(logging.DEBUG) await setup_samsungtv(hass, MOCK_CONFIG) - remote.close = mock.Mock(side_effect=OSError("BOOM")) + remote.close = Mock(side_effect=OSError("BOOM")) assert await hass.services.async_call( DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: ENTITY_ID}, True ) diff --git a/tests/components/sense/test_config_flow.py b/tests/components/sense/test_config_flow.py index fdce335b7cf..5a38090b5c9 100644 --- a/tests/components/sense/test_config_flow.py +++ b/tests/components/sense/test_config_flow.py @@ -1,10 +1,11 @@ """Test the Sense config flow.""" -from asynctest import patch from sense_energy import SenseAPITimeoutException, SenseAuthenticationException from homeassistant import config_entries, setup from homeassistant.components.sense.const import DOMAIN +from tests.async_mock import patch + async def test_form(hass): """Test we get the form.""" diff --git a/tests/components/sentry/test_config_flow.py b/tests/components/sentry/test_config_flow.py index 22c4367ddf2..25353751f91 100644 --- a/tests/components/sentry/test_config_flow.py +++ b/tests/components/sentry/test_config_flow.py @@ -1,11 +1,10 @@ """Test the sentry config flow.""" -from asynctest import patch from sentry_sdk.utils import BadDsn from homeassistant import config_entries, setup from homeassistant.components.sentry.const import DOMAIN -from tests.common import mock_coro +from tests.async_mock import patch async def test_form(hass): @@ -19,12 +18,11 @@ async def test_form(hass): with patch( "homeassistant.components.sentry.config_flow.validate_input", - return_value=mock_coro({"title": "Sentry"}), + return_value={"title": "Sentry"}, ), patch( - "homeassistant.components.sentry.async_setup", return_value=mock_coro(True) + "homeassistant.components.sentry.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.sentry.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.sentry.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"dsn": "http://public@sentry.local/1"}, diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 156741d8c9b..8743ab27bd7 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -5,15 +5,13 @@ import tempfile from typing import Tuple import unittest -from asynctest import Mock, patch - from homeassistant.components import shell_command from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import get_test_home_assistant -@asyncio.coroutine def mock_process_creator(error: bool = False) -> asyncio.coroutine: """Mock a coroutine that creates a process when yielded.""" diff --git a/tests/components/shopping_list/conftest.py b/tests/components/shopping_list/conftest.py index f63363e2f63..8307a6845b1 100644 --- a/tests/components/shopping_list/conftest.py +++ b/tests/components/shopping_list/conftest.py @@ -1,9 +1,9 @@ """Shopping list test helpers.""" -from asynctest import patch import pytest from homeassistant.components.shopping_list import intent as sl_intent +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index f53636fc440..5dc0fd5698b 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -2,7 +2,6 @@ import json from unittest.mock import MagicMock, PropertyMock, mock_open -from asynctest import patch from simplipy.errors import SimplipyError from homeassistant import data_entry_flow @@ -10,6 +9,7 @@ from homeassistant.components.simplisafe import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index e05917c17d8..a47b06ee637 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -2,7 +2,6 @@ import secrets from uuid import uuid4 -from asynctest import Mock, patch from pysmartthings import ( CLASSIFICATION_AUTOMATION, AppEntity, @@ -42,6 +41,7 @@ from homeassistant.config_entries import CONN_CLASS_CLOUD_PUSH, SOURCE_USER, Con from homeassistant.const import CONF_ACCESS_TOKEN, CONF_WEBHOOK_ID from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry COMPONENT_PREFIX = "homeassistant.components.smartthings." diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 81dbab917a3..ca964b9d1da 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -2,7 +2,6 @@ from uuid import uuid4 from aiohttp import ClientResponseError -from asynctest import Mock, patch from pysmartthings import APIResponseError from pysmartthings.installedapp import format_install_url @@ -23,7 +22,8 @@ from homeassistant.const import ( HTTP_UNAUTHORIZED, ) -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import AsyncMock, Mock, patch +from tests.common import MockConfigEntry async def test_import_shows_user_step(hass): @@ -342,8 +342,8 @@ async def test_entry_created_with_cloudhook( installed_app_id = str(uuid4()) refresh_token = str(uuid4()) smartthings_mock.apps.return_value = [] - smartthings_mock.create_app.return_value = (app, app_oauth_client) - smartthings_mock.locations.return_value = [location] + smartthings_mock.create_app = AsyncMock(return_value=(app, app_oauth_client)) + smartthings_mock.locations = AsyncMock(return_value=[location]) request = Mock() request.installed_app_id = installed_app_id request.auth_token = token @@ -351,11 +351,11 @@ async def test_entry_created_with_cloudhook( request.refresh_token = refresh_token with patch.object( - hass.components.cloud, "async_active_subscription", return_value=True + hass.components.cloud, "async_active_subscription", Mock(return_value=True) ), patch.object( hass.components.cloud, "async_create_cloudhook", - return_value=mock_coro("http://cloud.test"), + AsyncMock(return_value="http://cloud.test"), ) as mock_create_cloudhook: await smartapp.setup_smartapp_endpoint(hass) diff --git a/tests/components/smartthings/test_init.py b/tests/components/smartthings/test_init.py index 78142ae3fc4..0fdbf5c255a 100644 --- a/tests/components/smartthings/test_init.py +++ b/tests/components/smartthings/test_init.py @@ -2,7 +2,6 @@ from uuid import uuid4 from aiohttp import ClientConnectionError, ClientResponseError -from asynctest import Mock, patch from pysmartthings import InstalledAppStatus, OAuthToken import pytest @@ -22,6 +21,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/smartthings/test_smartapp.py b/tests/components/smartthings/test_smartapp.py index efc4844cef2..458e5f8ce27 100644 --- a/tests/components/smartthings/test_smartapp.py +++ b/tests/components/smartthings/test_smartapp.py @@ -1,7 +1,6 @@ """Tests for the smartapp module.""" from uuid import uuid4 -from asynctest import CoroutineMock, Mock, patch from pysmartthings import AppEntity, Capability from homeassistant.components.smartthings import smartapp @@ -11,6 +10,7 @@ from homeassistant.components.smartthings.const import ( DOMAIN, ) +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry @@ -76,11 +76,11 @@ async def test_smartapp_uninstall(hass, config_entry): async def test_smartapp_webhook(hass): """Test the smartapp webhook calls the manager.""" manager = Mock() - manager.handle_request = CoroutineMock(return_value={}) + manager.handle_request = AsyncMock(return_value={}) hass.data[DOMAIN][DATA_MANAGER] = manager request = Mock() request.headers = [] - request.json = CoroutineMock(return_value={}) + request.json = AsyncMock(return_value={}) result = await smartapp.smartapp_webhook(hass, "", request) assert result.body == b"{}" diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index 6065c323b91..56a0745c1b3 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -1,11 +1,10 @@ """Tests for SMHI config flow.""" -from asynctest import Mock, patch from smhi.smhi_lib import Smhi as SmhiApi, SmhiForecastException from homeassistant.components.smhi import config_flow from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE -from tests.common import mock_coro +from tests.async_mock import Mock, patch # pylint: disable=protected-access @@ -14,7 +13,7 @@ async def test_homeassistant_location_exists() -> None: hass = Mock() flow = config_flow.SmhiFlowHandler() flow.hass = hass - with patch.object(flow, "_check_location", return_value=mock_coro(True)): + with patch.object(flow, "_check_location", return_value=True): # Test exists hass.config.location_name = "Home" hass.config.latitude = 17.8419 @@ -99,7 +98,7 @@ async def test_flow_with_home_location(hass) -> None: flow = config_flow.SmhiFlowHandler() flow.hass = hass - with patch.object(flow, "_check_location", return_value=mock_coro(True)): + with patch.object(flow, "_check_location", return_value=True): hass.config.location_name = "Home" hass.config.latitude = 17.8419 hass.config.longitude = 59.3262 @@ -121,9 +120,9 @@ async def test_flow_show_form() -> None: # Test show form when Home Assistant config exists and # home is already configured, then new config is allowed with patch.object( - flow, "_show_config_form", return_value=mock_coro() + flow, "_show_config_form", return_value=None ) as config_form, patch.object( - flow, "_homeassistant_location_exists", return_value=mock_coro(True) + flow, "_homeassistant_location_exists", return_value=True ), patch.object( config_flow, "smhi_locations", @@ -135,9 +134,9 @@ async def test_flow_show_form() -> None: # Test show form when Home Assistant config not and # home is not configured with patch.object( - flow, "_show_config_form", return_value=mock_coro() + flow, "_show_config_form", return_value=None ) as config_form, patch.object( - flow, "_homeassistant_location_exists", return_value=mock_coro(False) + flow, "_homeassistant_location_exists", return_value=False ), patch.object( config_flow, "smhi_locations", @@ -160,7 +159,7 @@ async def test_flow_show_form_name_exists() -> None: # Test show form when Home Assistant config exists and # home is already configured, then new config is allowed with patch.object( - flow, "_show_config_form", return_value=mock_coro() + flow, "_show_config_form", return_value=None ) as config_form, patch.object( flow, "_name_in_configuration_exists", return_value=True ), patch.object( @@ -168,7 +167,7 @@ async def test_flow_show_form_name_exists() -> None: "smhi_locations", return_value={"test": "something", "name_exist": "config"}, ), patch.object( - flow, "_check_location", return_value=mock_coro(True) + flow, "_check_location", return_value=True ): await flow.async_step_user(user_input=test_data) @@ -190,17 +189,17 @@ async def test_flow_entry_created_from_user_input() -> None: # Test that entry created when user_input name not exists with patch.object( - flow, "_show_config_form", return_value=mock_coro() + flow, "_show_config_form", return_value=None ) as config_form, patch.object( flow, "_name_in_configuration_exists", return_value=False ), patch.object( - flow, "_homeassistant_location_exists", return_value=mock_coro(False) + flow, "_homeassistant_location_exists", return_value=False ), patch.object( config_flow, "smhi_locations", return_value={"test": "something", "name_exist": "config"}, ), patch.object( - flow, "_check_location", return_value=mock_coro(True) + flow, "_check_location", return_value=True ): result = await flow.async_step_user(user_input=test_data) @@ -223,20 +222,18 @@ async def test_flow_entry_created_user_input_faulty() -> None: test_data = {"name": "home", CONF_LONGITUDE: "0", CONF_LATITUDE: "0"} # Test that entry created when user_input name not exists - with patch.object( - flow, "_check_location", return_value=mock_coro(True) - ), patch.object( - flow, "_show_config_form", return_value=mock_coro() + with patch.object(flow, "_check_location", return_value=True), patch.object( + flow, "_show_config_form", return_value=None ) as config_form, patch.object( flow, "_name_in_configuration_exists", return_value=False ), patch.object( - flow, "_homeassistant_location_exists", return_value=mock_coro(False) + flow, "_homeassistant_location_exists", return_value=False ), patch.object( config_flow, "smhi_locations", return_value={"test": "something", "name_exist": "config"}, ), patch.object( - flow, "_check_location", return_value=mock_coro(False) + flow, "_check_location", return_value=False ): await flow.async_step_user(user_input=test_data) @@ -253,7 +250,7 @@ async def test_check_location_correct() -> None: with patch.object( config_flow.aiohttp_client, "async_get_clientsession" - ), patch.object(SmhiApi, "async_get_forecast", return_value=mock_coro()): + ), patch.object(SmhiApi, "async_get_forecast", return_value=None): assert await flow._check_location("58", "17") is True diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 357b670a38a..6f5fdfd2333 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -3,7 +3,7 @@ import asyncio from datetime import datetime import logging -from asynctest import Mock, patch +from smhi.smhi_lib import APIURL_TEMPLATE, SmhiForecastException from homeassistant.components.smhi import weather as weather_smhi from homeassistant.components.smhi.const import ATTR_SMHI_CLOUDINESS @@ -25,6 +25,7 @@ from homeassistant.components.weather import ( from homeassistant.const import TEMP_CELSIUS from homeassistant.core import HomeAssistant +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry, load_fixture _LOGGER = logging.getLogger(__name__) @@ -40,8 +41,6 @@ async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None: "async_forward_entry_setup". The actual result are tested with the entity state rather than "per function" unity tests """ - from smhi.smhi_lib import APIURL_TEMPLATE - uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"]) api_response = load_fixture("smhi.json") aioclient_mock.get(uri, text=api_response) @@ -150,7 +149,6 @@ def test_properties_unknown_symbol() -> None: # pylint: disable=protected-access async def test_refresh_weather_forecast_exceeds_retries(hass) -> None: """Test the refresh weather forecast function.""" - from smhi.smhi_lib import SmhiForecastException with patch.object( hass.helpers.event, "async_call_later" @@ -192,7 +190,6 @@ async def test_refresh_weather_forecast_timeout(hass) -> None: async def test_refresh_weather_forecast_exception() -> None: """Test any exception.""" - from smhi.smhi_lib import SmhiForecastException hass = Mock() weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022") @@ -200,17 +197,9 @@ async def test_refresh_weather_forecast_exception() -> None: with patch.object( hass.helpers.event, "async_call_later" - ) as call_later, patch.object(weather_smhi, "async_timeout"), patch.object( - weather_smhi.SmhiWeather, "retry_update" - ), patch.object( - weather_smhi.SmhiWeather, - "get_weather_forecast", - side_effect=SmhiForecastException(), + ) as call_later, patch.object( + weather, "get_weather_forecast", side_effect=SmhiForecastException(), ): - - hass.async_add_job = Mock() - call_later = hass.helpers.event.async_call_later - await weather.async_update() assert len(call_later.mock_calls) == 1 # Assert we are going to wait RETRY_TIMEOUT seconds @@ -223,8 +212,8 @@ async def test_retry_update(): weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022") weather.hass = hass - with patch.object(weather_smhi.SmhiWeather, "async_update") as update: - await weather.retry_update() + with patch.object(weather, "async_update", AsyncMock()) as update: + await weather.retry_update(None) assert len(update.mock_calls) == 1 diff --git a/tests/components/solarlog/test_config_flow.py b/tests/components/solarlog/test_config_flow.py index 4d8c11074db..1474b8e13e7 100644 --- a/tests/components/solarlog/test_config_flow.py +++ b/tests/components/solarlog/test_config_flow.py @@ -1,5 +1,4 @@ """Test the solarlog config flow.""" -from asynctest import patch import pytest from homeassistant import config_entries, data_entry_flow, setup @@ -7,7 +6,8 @@ from homeassistant.components.solarlog import config_flow from homeassistant.components.solarlog.const import DEFAULT_HOST, DOMAIN from homeassistant.const import CONF_HOST, CONF_NAME -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry NAME = "Solarlog test 1 2 3" HOST = "http://1.1.1.1" @@ -24,12 +24,11 @@ async def test_form(hass): with patch( "homeassistant.components.solarlog.config_flow.SolarLogConfigFlow._test_connection", - return_value=mock_coro({"title": "solarlog test 1 2 3"}), + return_value={"title": "solarlog test 1 2 3"}, ), patch( - "homeassistant.components.solarlog.async_setup", return_value=mock_coro(True) + "homeassistant.components.solarlog.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.solarlog.async_setup_entry", - return_value=mock_coro(True), + "homeassistant.components.solarlog.async_setup_entry", return_value=True, ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": HOST, "name": NAME} @@ -48,7 +47,7 @@ def mock_controller(): """Mock a successful _host_in_configuration_exists.""" with patch( "homeassistant.components.solarlog.config_flow.SolarLogConfigFlow._test_connection", - side_effect=lambda *_: mock_coro(True), + return_value=True, ): yield diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 246d1eb1627..20c1eb10320 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -1,11 +1,11 @@ """Configuration for Sonos tests.""" -from asynctest.mock import Mock, patch as patch import pytest from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.sonos import DOMAIN from homeassistant.const import CONF_HOSTS +from tests.async_mock import Mock, patch as patch from tests.common import MockConfigEntry diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index ea580656b24..bc3cc1bc977 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -1,7 +1,6 @@ """Test the Soundtouch component.""" from unittest.mock import call -from asynctest import patch from libsoundtouch.device import ( Config, Preset, @@ -28,6 +27,8 @@ from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.helpers.discovery import async_load_platform from homeassistant.setup import async_setup_component +from tests.async_mock import patch + # pylint: disable=super-init-not-called diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py index 0661a5a9738..12fa8e8a4d6 100644 --- a/tests/components/stream/test_init.py +++ b/tests/components/stream/test_init.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_FILENAME from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import AsyncMock async def test_record_service_invalid_file(hass): @@ -76,7 +76,7 @@ async def test_record_service_lookback(hass): hls_mock = MagicMock() hls_mock.num_segments = 3 hls_mock.target_duration = 2 - hls_mock.recv.return_value = mock_coro() + hls_mock.recv = AsyncMock(return_value=None) stream_mock.return_value.outputs = {"hls": hls_mock} # Call Service diff --git a/tests/components/switcher_kis/conftest.py b/tests/components/switcher_kis/conftest.py index 2b0150cae67..37d61d6ca19 100644 --- a/tests/components/switcher_kis/conftest.py +++ b/tests/components/switcher_kis/conftest.py @@ -4,7 +4,6 @@ from asyncio import Queue from datetime import datetime from typing import Any, Generator, Optional -from asynctest import CoroutineMock, patch from pytest import fixture from .consts import ( @@ -20,6 +19,8 @@ from .consts import ( DUMMY_REMAINING_TIME, ) +from tests.async_mock import AsyncMock, patch + @patch("aioswitcher.devices.SwitcherV2Device") class MockSwitcherV2Device: @@ -100,7 +101,7 @@ def mock_bridge_fixture() -> Generator[None, Any, None]: await queue.put(MockSwitcherV2Device()) return await queue.get() - mock_bridge = CoroutineMock() + mock_bridge = AsyncMock() patchers = [ patch( @@ -163,9 +164,9 @@ def mock_failed_bridge_fixture() -> Generator[None, Any, None]: @fixture(name="mock_api") -def mock_api_fixture() -> Generator[CoroutineMock, Any, None]: +def mock_api_fixture() -> Generator[AsyncMock, Any, None]: """Fixture for mocking aioswitcher.api.SwitcherV2Api.""" - mock_api = CoroutineMock() + mock_api = AsyncMock() patchers = [ patch( diff --git a/tests/components/tado/test_config_flow.py b/tests/components/tado/test_config_flow.py index 0c75eadfb0e..2e42a2bc1fb 100644 --- a/tests/components/tado/test_config_flow.py +++ b/tests/components/tado/test_config_flow.py @@ -1,11 +1,11 @@ """Test the Tado config flow.""" -from asynctest import MagicMock, patch import requests from homeassistant import config_entries, setup from homeassistant.components.tado.const import DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry diff --git a/tests/components/tesla/test_config_flow.py b/tests/components/tesla/test_config_flow.py index 24cdb375fde..a59daee9ac6 100644 --- a/tests/components/tesla/test_config_flow.py +++ b/tests/components/tesla/test_config_flow.py @@ -1,5 +1,4 @@ """Test the Tesla config flow.""" -from asynctest import patch from teslajsonpy import TeslaException from homeassistant import config_entries, data_entry_flow, setup @@ -19,7 +18,8 @@ from homeassistant.const import ( HTTP_NOT_FOUND, ) -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_form(hass): @@ -33,11 +33,11 @@ async def test_form(hass): with patch( "homeassistant.components.tesla.config_flow.TeslaAPI.connect", - return_value=mock_coro(("test-refresh-token", "test-access-token")), + return_value=("test-refresh-token", "test-access-token"), ), patch( - "homeassistant.components.tesla.async_setup", return_value=mock_coro(True) + "homeassistant.components.tesla.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.tesla.async_setup_entry", return_value=mock_coro(True) + "homeassistant.components.tesla.async_setup_entry", return_value=True ) as mock_setup_entry: result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_PASSWORD: "test", CONF_USERNAME: "test@email.com"} @@ -102,7 +102,7 @@ async def test_form_repeat_identifier(hass): ) with patch( "homeassistant.components.tesla.config_flow.TeslaAPI.connect", - return_value=mock_coro(("test-refresh-token", "test-access-token")), + return_value=("test-refresh-token", "test-access-token"), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -118,7 +118,7 @@ async def test_import(hass): with patch( "homeassistant.components.tesla.config_flow.TeslaAPI.connect", - return_value=mock_coro(("test-refresh-token", "test-access-token")), + return_value=("test-refresh-token", "test-access-token"), ): result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py index bb1a0e7cc64..891dd1377fe 100644 --- a/tests/components/tradfri/conftest.py +++ b/tests/components/tradfri/conftest.py @@ -1,7 +1,8 @@ """Common tradfri test fixtures.""" -from asynctest import patch import pytest +from tests.async_mock import patch + @pytest.fixture def mock_gateway_info(): diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py index 45e18be83d3..43e33706bf6 100644 --- a/tests/components/tradfri/test_config_flow.py +++ b/tests/components/tradfri/test_config_flow.py @@ -1,10 +1,10 @@ """Test the Tradfri config flow.""" -from asynctest import patch import pytest from homeassistant import data_entry_flow from homeassistant.components.tradfri import config_flow +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py index bdb45ee4306..2845137244b 100644 --- a/tests/components/tradfri/test_init.py +++ b/tests/components/tradfri/test_init.py @@ -1,8 +1,7 @@ """Tests for Tradfri setup.""" -from asynctest import patch - from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index 4979efc22dc..b8eddb9c785 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -2,7 +2,6 @@ import re import unittest -from asynctest import patch import requests_mock from homeassistant.components.uk_transport.sensor import ( @@ -20,6 +19,7 @@ from homeassistant.components.uk_transport.sensor import ( from homeassistant.setup import setup_component from homeassistant.util.dt import now +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture BUS_ATCOCODE = "340000368SHE" diff --git a/tests/components/unifi/conftest.py b/tests/components/unifi/conftest.py index 189b80c1932..8ce7cef0345 100644 --- a/tests/components/unifi/conftest.py +++ b/tests/components/unifi/conftest.py @@ -1,7 +1,8 @@ """Fixtures for UniFi methods.""" -from asynctest import patch import pytest +from tests.async_mock import patch + @pytest.fixture(autouse=True) def mock_discovery(): diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index bad191e1600..ae738ba8a64 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -1,6 +1,5 @@ """Test UniFi config flow.""" import aiounifi -from asynctest import patch from homeassistant import data_entry_flow from homeassistant.components.unifi.const import ( @@ -27,6 +26,7 @@ from homeassistant.const import ( from .test_controller import setup_unifi_integration +from tests.async_mock import patch from tests.common import MockConfigEntry CLIENTS = [{"mac": "00:00:00:00:00:01"}] diff --git a/tests/components/unifi/test_controller.py b/tests/components/unifi/test_controller.py index 824ffbffc41..9bf956e8063 100644 --- a/tests/components/unifi/test_controller.py +++ b/tests/components/unifi/test_controller.py @@ -4,7 +4,6 @@ from copy import deepcopy from datetime import timedelta import aiounifi -from asynctest import patch import pytest from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -35,6 +34,7 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry CONTROLLER_HOST = { diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index df7c49a1bf7..5f40f098d39 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -4,7 +4,6 @@ from datetime import timedelta from aiounifi.controller import MESSAGE_CLIENT_REMOVED, SIGNAL_CONNECTION_STATE from aiounifi.websocket import SIGNAL_DATA, STATE_DISCONNECTED, STATE_RUNNING -from asynctest import patch from homeassistant import config_entries from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN @@ -24,6 +23,8 @@ import homeassistant.util.dt as dt_util from .test_controller import ENTRY_CONFIG, setup_unifi_integration +from tests.async_mock import patch + CLIENT_1 = { "essid": "ssid", "hostname": "client_1", diff --git a/tests/components/unifi/test_init.py b/tests/components/unifi/test_init.py index db6c1e30748..80e3e07fa17 100644 --- a/tests/components/unifi/test_init.py +++ b/tests/components/unifi/test_init.py @@ -7,6 +7,7 @@ from homeassistant.setup import async_setup_component from .test_controller import setup_unifi_integration +from tests.async_mock import AsyncMock from tests.common import MockConfigEntry, mock_coro @@ -25,7 +26,7 @@ async def test_successful_config_entry(hass): async def test_controller_fail_setup(hass): """Test that a failed setup still stores controller.""" with patch("homeassistant.components.unifi.UniFiController") as mock_controller: - mock_controller.return_value.async_setup.return_value = mock_coro(False) + mock_controller.return_value.async_setup = AsyncMock(return_value=False) await setup_unifi_integration(hass) assert hass.data[UNIFI_DOMAIN] == {} @@ -55,7 +56,7 @@ async def test_controller_no_mac(hass): "homeassistant.helpers.device_registry.async_get_registry", return_value=mock_coro(mock_registry), ): - mock_controller.return_value.async_setup.return_value = mock_coro(True) + mock_controller.return_value.async_setup = AsyncMock(return_value=True) mock_controller.return_value.mac = None assert await unifi.async_setup_entry(hass, entry) is True diff --git a/tests/components/unifi_direct/test_device_tracker.py b/tests/components/unifi_direct/test_device_tracker.py index 692231d4cad..be58efa415a 100644 --- a/tests/components/unifi_direct/test_device_tracker.py +++ b/tests/components/unifi_direct/test_device_tracker.py @@ -2,7 +2,6 @@ from datetime import timedelta import os -from asynctest import mock, patch import pytest import voluptuous as vol @@ -23,6 +22,7 @@ from homeassistant.components.unifi_direct.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, load_fixture, mock_component scanner_path = "homeassistant.components.unifi_direct.device_tracker.UnifiDeviceScanner" @@ -38,7 +38,7 @@ def setup_comp(hass): os.remove(yaml_devices) -@patch(scanner_path, return_value=mock.MagicMock(spec=UnifiDeviceScanner)) +@patch(scanner_path, return_value=MagicMock(spec=UnifiDeviceScanner)) async def test_get_scanner(unifi_mock, hass): """Test creating an Unifi direct scanner with a password.""" conf_dict = { @@ -57,7 +57,7 @@ async def test_get_scanner(unifi_mock, hass): assert await async_setup_component(hass, DOMAIN, conf_dict) conf_dict[DOMAIN][CONF_PORT] = 22 - assert unifi_mock.call_args == mock.call(conf_dict[DOMAIN]) + assert unifi_mock.call_args == call(conf_dict[DOMAIN]) @patch("pexpect.pxssh.pxssh") diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index e583f4e0114..95875828c71 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -1,14 +1,12 @@ """The tests for the Updater component.""" -from unittest.mock import Mock - -from asynctest import patch import pytest from homeassistant.components import updater from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.setup import async_setup_component -from tests.common import MockDependency, mock_component, mock_coro +from tests.async_mock import patch +from tests.common import MockDependency, mock_component NEW_VERSION = "10000.0" MOCK_VERSION = "10.0" @@ -75,7 +73,7 @@ async def test_same_version_shows_entity_false( ): """Test if sensor is false if no new version is available.""" mock_get_uuid.return_value = MOCK_HUUID - mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, "")) + mock_get_newest_version.return_value = (MOCK_VERSION, "") assert await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}}) @@ -92,7 +90,7 @@ async def test_same_version_shows_entity_false( async def test_disable_reporting(hass, mock_get_uuid, mock_get_newest_version): """Test we do not gather analytics when disable reporting is active.""" mock_get_uuid.return_value = MOCK_HUUID - mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, "")) + mock_get_newest_version.return_value = (MOCK_VERSION, "") assert await async_setup_component( hass, updater.DOMAIN, {updater.DOMAIN: {"reporting": False}} @@ -123,7 +121,7 @@ async def test_get_newest_version_analytics_when_huuid(hass, aioclient_mock): with patch( "homeassistant.helpers.system_info.async_get_system_info", - Mock(return_value=mock_coro({"fake": "bla"})), + return_value={"fake": "bla"}, ): res = await updater.get_newest_version(hass, MOCK_HUUID, False) assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"]) @@ -135,7 +133,7 @@ async def test_error_fetching_new_version_bad_json(hass, aioclient_mock): with patch( "homeassistant.helpers.system_info.async_get_system_info", - Mock(return_value=mock_coro({"fake": "bla"})), + return_value={"fake": "bla"}, ), pytest.raises(UpdateFailed): await updater.get_newest_version(hass, MOCK_HUUID, False) @@ -152,7 +150,7 @@ async def test_error_fetching_new_version_invalid_response(hass, aioclient_mock) with patch( "homeassistant.helpers.system_info.async_get_system_info", - Mock(return_value=mock_coro({"fake": "bla"})), + return_value={"fake": "bla"}, ), pytest.raises(UpdateFailed): await updater.get_newest_version(hass, MOCK_HUUID, False) diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 464ccc90675..7c43c24cdc3 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -2,13 +2,12 @@ from ipaddress import IPv4Address -from asynctest import patch - from homeassistant.components import upnp from homeassistant.components.upnp.device import Device from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry, mock_coro @@ -86,8 +85,8 @@ async def test_async_setup_entry_default(hass): {"udn": udn, "ssdp_description": "http://192.168.1.1/desc.xml"} ] - create_device.return_value = mock_coro(return_value=mock_device) - async_discover.return_value = mock_coro(return_value=discovery_infos) + create_device.return_value = mock_device + async_discover.return_value = discovery_infos assert await upnp.async_setup_entry(hass, entry) is True @@ -128,8 +127,8 @@ async def test_async_setup_entry_port_mapping(hass): {"udn": udn, "ssdp_description": "http://192.168.1.1/desc.xml"} ] - create_device.return_value = mock_coro(return_value=mock_device) - async_discover.return_value = mock_coro(return_value=discovery_infos) + create_device.return_value = mock_device + async_discover.return_value = discovery_infos assert await upnp.async_setup_entry(hass, entry) is True diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index a6208726451..f1e13a5f208 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -1,5 +1,4 @@ """Vera tests.""" -from asynctest import MagicMock import pyvera as pv from requests.exceptions import RequestException @@ -9,6 +8,7 @@ from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock from tests.common import MockConfigEntry diff --git a/tests/components/vilfo/test_config_flow.py b/tests/components/vilfo/test_config_flow.py index 9176a9a1970..d9e8a2ffd24 100644 --- a/tests/components/vilfo/test_config_flow.py +++ b/tests/components/vilfo/test_config_flow.py @@ -1,12 +1,11 @@ """Test the Vilfo Router config flow.""" -from asynctest.mock import patch import vilfo from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.vilfo.const import DOMAIN from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_ID, CONF_MAC -from tests.common import mock_coro +from tests.async_mock import patch async def test_form(hass): @@ -22,7 +21,7 @@ async def test_form(hass): with patch("vilfo.Client.ping", return_value=None), patch( "vilfo.Client.get_board_information", return_value=None ), patch("vilfo.Client.resolve_mac_address", return_value=mock_mac), patch( - "homeassistant.components.vilfo.async_setup", return_value=mock_coro(True) + "homeassistant.components.vilfo.async_setup", return_value=True ) as mock_setup, patch( "homeassistant.components.vilfo.async_setup_entry" ) as mock_setup_entry: diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index e630f201e12..f7448a71c31 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -1,5 +1,4 @@ """Configure py.test.""" -from asynctest import patch import pytest from pyvizio.const import DEVICE_CLASS_SPEAKER, MAX_VOLUME @@ -21,6 +20,8 @@ from .const import ( MockStartPairingResponse, ) +from tests.async_mock import patch + class MockInput: """Mock Vizio device input.""" diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 1f6abf10563..aaf802af7ec 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -5,7 +5,6 @@ import logging from typing import Any, Dict, List, Optional from unittest.mock import call -from asynctest import patch import pytest from pytest import raises from pyvizio.api.apps import AppConfig @@ -74,6 +73,7 @@ from .const import ( VOLUME_STEP, ) +from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 9ba35a510dd..cc889eca064 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -27,7 +27,7 @@ from homeassistant.setup import async_setup_component if sys.version_info >= (3, 8, 0): from unittest.mock import patch else: - from asynctest import patch + from tests.async_mock import patch NAME = "fake" diff --git a/tests/components/websocket_api/test_http.py b/tests/components/websocket_api/test_http.py index 9082337ccc8..e76cbe0dbdc 100644 --- a/tests/components/websocket_api/test_http.py +++ b/tests/components/websocket_api/test_http.py @@ -2,12 +2,12 @@ from datetime import timedelta from aiohttp import WSMsgType -from asynctest import patch import pytest from homeassistant.components.websocket_api import const, http from homeassistant.util.dt import utcnow +from tests.async_mock import patch from tests.common import async_fire_time_changed diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index 65ff65ebbd8..f0f3a160332 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -2,7 +2,6 @@ from datetime import timedelta from unittest.mock import patch -from asynctest import MagicMock import pytest from withings_api import WithingsApi from withings_api.common import TimeoutException, UnauthorizedException @@ -14,6 +13,8 @@ from homeassistant.components.withings.common import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.util import dt +from tests.async_mock import MagicMock + @pytest.fixture(name="withings_api") def withings_api_fixture() -> WithingsApi: diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index f6f36ba8eff..5bb3e8534c3 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -2,7 +2,6 @@ import re import time -from asynctest import MagicMock import requests_mock import voluptuous as vol from withings_api import AbstractWithingsApi @@ -32,6 +31,8 @@ from .common import ( setup_hass, ) +from tests.async_mock import MagicMock + def config_schema_validate(withings_config) -> None: """Assert a schema config succeeds.""" diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 0009677cf18..f2cc5514a2e 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -1,6 +1,5 @@ """Tests for the WLED light platform.""" import aiohttp -from asynctest.mock import patch from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -32,6 +31,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant +from tests.async_mock import patch from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/wled/test_sensor.py b/tests/components/wled/test_sensor.py index d77bd99b97c..66fedfdd274 100644 --- a/tests/components/wled/test_sensor.py +++ b/tests/components/wled/test_sensor.py @@ -1,7 +1,6 @@ """Tests for the WLED sensor platform.""" from datetime import datetime -from asynctest import patch import pytest from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -21,6 +20,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index d140953b948..7d411984cff 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -1,6 +1,5 @@ """Tests for the WLED switch platform.""" import aiohttp -from asynctest.mock import patch from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.wled.const import ( @@ -20,6 +19,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant +from tests.async_mock import patch from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/wwlln/test_config_flow.py b/tests/components/wwlln/test_config_flow.py index e9e32ae75e1..b5d34f542e3 100644 --- a/tests/components/wwlln/test_config_flow.py +++ b/tests/components/wwlln/test_config_flow.py @@ -1,6 +1,4 @@ """Define tests for the WWLLN config flow.""" -from asynctest import patch - from homeassistant import data_entry_flow from homeassistant.components.wwlln import ( CONF_WINDOW, @@ -11,6 +9,7 @@ from homeassistant.components.wwlln import ( from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/xiaomi/test_device_tracker.py b/tests/components/xiaomi/test_device_tracker.py index 5d65aee7616..1760b3274a4 100644 --- a/tests/components/xiaomi/test_device_tracker.py +++ b/tests/components/xiaomi/test_device_tracker.py @@ -1,7 +1,6 @@ """The tests for the Xiaomi router device tracker platform.""" import logging -from asynctest import mock, patch import requests from homeassistant.components.device_tracker import DOMAIN @@ -9,6 +8,8 @@ import homeassistant.components.xiaomi.device_tracker as xiaomi from homeassistant.components.xiaomi.device_tracker import get_scanner from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PLATFORM, CONF_USERNAME +from tests.async_mock import MagicMock, call, patch + _LOGGER = logging.getLogger(__name__) INVALID_USERNAME = "bob" @@ -144,7 +145,7 @@ def mocked_requests(*args, **kwargs): @patch( "homeassistant.components.xiaomi.device_tracker.XiaomiDeviceScanner", - return_value=mock.MagicMock(), + return_value=MagicMock(), ) async def test_config(xiaomi_mock, hass): """Testing minimal configuration.""" @@ -159,7 +160,7 @@ async def test_config(xiaomi_mock, hass): } xiaomi.get_scanner(hass, config) assert xiaomi_mock.call_count == 1 - assert xiaomi_mock.call_args == mock.call(config[DOMAIN]) + assert xiaomi_mock.call_args == call(config[DOMAIN]) call_arg = xiaomi_mock.call_args[0][0] assert call_arg["username"] == "admin" assert call_arg["password"] == "passwordTest" @@ -169,7 +170,7 @@ async def test_config(xiaomi_mock, hass): @patch( "homeassistant.components.xiaomi.device_tracker.XiaomiDeviceScanner", - return_value=mock.MagicMock(), + return_value=MagicMock(), ) async def test_config_full(xiaomi_mock, hass): """Testing full configuration.""" @@ -185,7 +186,7 @@ async def test_config_full(xiaomi_mock, hass): } xiaomi.get_scanner(hass, config) assert xiaomi_mock.call_count == 1 - assert xiaomi_mock.call_args == mock.call(config[DOMAIN]) + assert xiaomi_mock.call_args == call(config[DOMAIN]) call_arg = xiaomi_mock.call_args[0][0] assert call_arg["username"] == "alternativeAdminName" assert call_arg["password"] == "passwordTest" diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 2c1411ded68..023deb93c81 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -1,13 +1,14 @@ """Test the Xiaomi Miio config flow.""" from unittest.mock import Mock -from asynctest import patch from miio import DeviceException from homeassistant import config_entries from homeassistant.components.xiaomi_miio import config_flow, const from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN +from tests.async_mock import patch + TEST_HOST = "1.2.3.4" TEST_TOKEN = "12345678901234567890123456789012" TEST_NAME = "Test_Gateway" diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 27824623d23..a8e22e74bc4 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -1,5 +1,4 @@ """Test Zeroconf component setup process.""" -from asynctest import patch import pytest from zeroconf import ServiceInfo, ServiceStateChange @@ -7,6 +6,8 @@ from homeassistant.components import zeroconf from homeassistant.generated import zeroconf as zc_gen from homeassistant.setup import async_setup_component +from tests.async_mock import patch + NON_UTF8_VALUE = b"ABCDEF\x8a" NON_ASCII_KEY = b"non-ascii-key\x8a" PROPERTIES = { diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 2542037a2bf..9b139317825 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -2,7 +2,6 @@ import time from unittest.mock import Mock -from asynctest import CoroutineMock from zigpy.device import Device as zigpy_dev from zigpy.endpoint import Endpoint as zigpy_ep import zigpy.profiles.zha @@ -15,6 +14,8 @@ import zigpy.zdo.types import homeassistant.components.zha.core.const as zha_const from homeassistant.util import slugify +from tests.async_mock import AsyncMock + class FakeEndpoint: """Fake endpoint for moking zigpy.""" @@ -32,7 +33,7 @@ class FakeEndpoint: self.model = model self.profile_id = zigpy.profiles.zha.PROFILE_ID self.device_type = None - self.request = CoroutineMock() + self.request = AsyncMock() def add_input_cluster(self, cluster_id): """Add an input cluster.""" @@ -64,16 +65,16 @@ FakeEndpoint.add_to_group = zigpy_ep.add_to_group def patch_cluster(cluster): """Patch a cluster for testing.""" - cluster.bind = CoroutineMock(return_value=[0]) - cluster.configure_reporting = CoroutineMock(return_value=[0]) + cluster.bind = AsyncMock(return_value=[0]) + cluster.configure_reporting = AsyncMock(return_value=[0]) cluster.deserialize = Mock() cluster.handle_cluster_request = Mock() - cluster.read_attributes = CoroutineMock(return_value=[{}, {}]) + cluster.read_attributes = AsyncMock(return_value=[{}, {}]) cluster.read_attributes_raw = Mock() - cluster.unbind = CoroutineMock(return_value=[0]) - cluster.write_attributes = CoroutineMock(return_value=[0]) + cluster.unbind = AsyncMock(return_value=[0]) + cluster.write_attributes = AsyncMock(return_value=[0]) if cluster.cluster_id == 4: - cluster.add = CoroutineMock(return_value=[0]) + cluster.add = AsyncMock(return_value=[0]) class FakeDevice: @@ -96,7 +97,7 @@ class FakeDevice: self.manufacturer = manufacturer self.model = model self.node_desc = zigpy.zdo.types.NodeDescriptor() - self.remove_from_group = CoroutineMock() + self.remove_from_group = AsyncMock() if node_desc is None: node_desc = b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00" self.node_desc = zigpy.zdo.types.NodeDescriptor.deserialize(node_desc)[0] diff --git a/tests/components/zha/conftest.py b/tests/components/zha/conftest.py index b83db53533c..e737f990163 100644 --- a/tests/components/zha/conftest.py +++ b/tests/components/zha/conftest.py @@ -1,7 +1,6 @@ """Test configuration for the ZHA component.""" from unittest import mock -import asynctest import pytest import zigpy from zigpy.application import ControllerApplication @@ -15,6 +14,7 @@ from homeassistant.setup import async_setup_component from .common import FakeDevice, FakeEndpoint, get_zha_gateway +import tests.async_mock from tests.common import MockConfigEntry FIXTURE_GRP_ID = 0x1001 @@ -25,8 +25,8 @@ FIXTURE_GRP_NAME = "fixture group" def zigpy_app_controller(): """Zigpy ApplicationController fixture.""" app = mock.MagicMock(spec_set=ControllerApplication) - app.startup = asynctest.CoroutineMock() - app.shutdown = asynctest.CoroutineMock() + app.startup = tests.async_mock.AsyncMock() + app.shutdown = tests.async_mock.AsyncMock() groups = zigpy.group.Groups(app) groups.add_group(FIXTURE_GRP_ID, FIXTURE_GRP_NAME, suppress_event=True) app.configure_mock(groups=groups) @@ -41,7 +41,7 @@ def zigpy_app_controller(): def zigpy_radio(): """Zigpy radio mock.""" radio = mock.MagicMock() - radio.connect = asynctest.CoroutineMock() + radio.connect = tests.async_mock.AsyncMock() return radio @@ -93,8 +93,8 @@ def channel(): ch.name = name ch.generic_id = f"channel_0x{cluster_id:04x}" ch.id = f"{endpoint_id}:0x{cluster_id:04x}" - ch.async_configure = asynctest.CoroutineMock() - ch.async_initialize = asynctest.CoroutineMock() + ch.async_configure = tests.async_mock.AsyncMock() + ch.async_initialize = tests.async_mock.AsyncMock() return ch return channel diff --git a/tests/components/zha/test_channels.py b/tests/components/zha/test_channels.py index 1196cdc3b40..471e44f8409 100644 --- a/tests/components/zha/test_channels.py +++ b/tests/components/zha/test_channels.py @@ -2,7 +2,6 @@ import asyncio from unittest import mock -import asynctest import pytest import zigpy.types as t import zigpy.zcl.clusters @@ -14,6 +13,8 @@ import homeassistant.components.zha.core.registries as registries from .common import get_zha_gateway, make_zcl_header +import tests.async_mock + @pytest.fixture def ieee(): @@ -379,12 +380,12 @@ async def test_ep_channels_configure(channel): ch_1 = channel(zha_const.CHANNEL_ON_OFF, 6) ch_2 = channel(zha_const.CHANNEL_LEVEL, 8) ch_3 = channel(zha_const.CHANNEL_COLOR, 768) - ch_3.async_configure = asynctest.CoroutineMock(side_effect=asyncio.TimeoutError) - ch_3.async_initialize = asynctest.CoroutineMock(side_effect=asyncio.TimeoutError) + ch_3.async_configure = tests.async_mock.AsyncMock(side_effect=asyncio.TimeoutError) + ch_3.async_initialize = tests.async_mock.AsyncMock(side_effect=asyncio.TimeoutError) ch_4 = channel(zha_const.CHANNEL_ON_OFF, 6) ch_5 = channel(zha_const.CHANNEL_LEVEL, 8) - ch_5.async_configure = asynctest.CoroutineMock(side_effect=asyncio.TimeoutError) - ch_5.async_initialize = asynctest.CoroutineMock(side_effect=asyncio.TimeoutError) + ch_5.async_configure = tests.async_mock.AsyncMock(side_effect=asyncio.TimeoutError) + ch_5.async_initialize = tests.async_mock.AsyncMock(side_effect=asyncio.TimeoutError) channels = mock.MagicMock(spec_set=zha_channels.Channels) type(channels).semaphore = mock.PropertyMock(return_value=asyncio.Semaphore(3)) @@ -420,8 +421,8 @@ async def test_poll_control_configure(poll_control_ch): async def test_poll_control_checkin_response(poll_control_ch): """Test poll control channel checkin response.""" - rsp_mock = asynctest.CoroutineMock() - set_interval_mock = asynctest.CoroutineMock() + rsp_mock = tests.async_mock.AsyncMock() + set_interval_mock = tests.async_mock.AsyncMock() cluster = poll_control_ch.cluster patch_1 = mock.patch.object(cluster, "checkin_response", rsp_mock) patch_2 = mock.patch.object(cluster, "set_long_poll_interval", set_interval_mock) @@ -442,7 +443,7 @@ async def test_poll_control_checkin_response(poll_control_ch): async def test_poll_control_cluster_command(hass, poll_control_device): """Test poll control channel response to cluster command.""" - checkin_mock = asynctest.CoroutineMock() + checkin_mock = tests.async_mock.AsyncMock() poll_control_ch = poll_control_device.channels.pools[0].all_channels["1:0x0020"] cluster = poll_control_ch.cluster diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 7e0b89f8d70..91d2ef75aa5 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -1,12 +1,11 @@ """Tests for ZHA config flow.""" from unittest import mock -import asynctest - from homeassistant.components.zha import config_flow from homeassistant.components.zha.core.const import CONTROLLER, DOMAIN, ZHA_GW_RADIO import homeassistant.components.zha.core.registries +import tests.async_mock from tests.common import MockConfigEntry @@ -15,7 +14,7 @@ async def test_user_flow(hass): flow = config_flow.ZhaFlowHandler() flow.hass = hass - with asynctest.patch( + with tests.async_mock.patch( "homeassistant.components.zha.config_flow.check_zigpy_connection", return_value=False, ): @@ -25,7 +24,7 @@ async def test_user_flow(hass): assert result["errors"] == {"base": "cannot_connect"} - with asynctest.patch( + with tests.async_mock.patch( "homeassistant.components.zha.config_flow.check_zigpy_connection", return_value=True, ): @@ -79,18 +78,18 @@ async def test_import_flow_existing_config_entry(hass): async def test_check_zigpy_connection(): """Test config flow validator.""" - mock_radio = asynctest.MagicMock() - mock_radio.connect = asynctest.CoroutineMock() - radio_cls = asynctest.MagicMock(return_value=mock_radio) + mock_radio = tests.async_mock.MagicMock() + mock_radio.connect = tests.async_mock.AsyncMock() + radio_cls = tests.async_mock.MagicMock(return_value=mock_radio) - bad_radio = asynctest.MagicMock() - bad_radio.connect = asynctest.CoroutineMock(side_effect=Exception) - bad_radio_cls = asynctest.MagicMock(return_value=bad_radio) + bad_radio = tests.async_mock.MagicMock() + bad_radio.connect = tests.async_mock.AsyncMock(side_effect=Exception) + bad_radio_cls = tests.async_mock.MagicMock(return_value=bad_radio) - mock_ctrl = asynctest.MagicMock() - mock_ctrl.startup = asynctest.CoroutineMock() - mock_ctrl.shutdown = asynctest.CoroutineMock() - ctrl_cls = asynctest.MagicMock(return_value=mock_ctrl) + mock_ctrl = tests.async_mock.MagicMock() + mock_ctrl.startup = tests.async_mock.AsyncMock() + mock_ctrl.shutdown = tests.async_mock.AsyncMock() + ctrl_cls = tests.async_mock.MagicMock(return_value=mock_ctrl) new_radios = { mock.sentinel.radio: {ZHA_GW_RADIO: radio_cls, CONTROLLER: ctrl_cls}, mock.sentinel.bad_radio: {ZHA_GW_RADIO: bad_radio_cls, CONTROLLER: ctrl_cls}, diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 188ddf69a23..b4c72fd82d4 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -1,7 +1,4 @@ """Test zha cover.""" -from unittest.mock import MagicMock, call, patch - -import asynctest import pytest import zigpy.types import zigpy.zcl.clusters.closures as closures @@ -17,6 +14,7 @@ from .common import ( send_attributes_report, ) +from tests.async_mock import MagicMock, call, patch from tests.common import mock_coro @@ -34,7 +32,7 @@ def zigpy_cover_device(zigpy_device_mock): return zigpy_device_mock(endpoints) -@asynctest.patch( +@patch( "homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize" ) async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): diff --git a/tests/components/zha/test_device.py b/tests/components/zha/test_device.py index c92f574825d..6e528299911 100644 --- a/tests/components/zha/test_device.py +++ b/tests/components/zha/test_device.py @@ -3,7 +3,6 @@ from datetime import timedelta import time from unittest import mock -import asynctest import pytest import zigpy.zcl.clusters.general as general @@ -13,6 +12,7 @@ import homeassistant.util.dt as dt_util from .common import async_enable_traffic, make_zcl_header +from tests.async_mock import patch from tests.common import async_fire_time_changed @@ -90,7 +90,7 @@ def _send_time_changed(hass, seconds): async_fire_time_changed(hass, now) -@asynctest.patch( +@patch( "homeassistant.components.zha.core.channels.general.BasicChannel.async_initialize", new=mock.MagicMock(), ) @@ -144,7 +144,7 @@ async def test_check_available_success( assert zha_device.available is True -@asynctest.patch( +@patch( "homeassistant.components.zha.core.channels.general.BasicChannel.async_initialize", new=mock.MagicMock(), ) @@ -187,7 +187,7 @@ async def test_check_available_unsuccessful( assert zha_device.available is False -@asynctest.patch( +@patch( "homeassistant.components.zha.core.channels.general.BasicChannel.async_initialize", new=mock.MagicMock(), ) diff --git a/tests/components/zha/test_discover.py b/tests/components/zha/test_discover.py index 8b1ce502a78..4b95040dd08 100644 --- a/tests/components/zha/test_discover.py +++ b/tests/components/zha/test_discover.py @@ -3,7 +3,6 @@ import re from unittest import mock -import asynctest import pytest import zigpy.quirks import zigpy.types @@ -30,6 +29,8 @@ import homeassistant.helpers.entity_registry from .common import get_zha_gateway from .zha_devices_list import DEVICES +from tests.async_mock import AsyncMock, patch + NO_TAIL_ID = re.compile("_\\d$") @@ -51,11 +52,9 @@ def channels_mock(zha_device_mock): return _mock -@asynctest.patch( +@patch( "zigpy.zcl.clusters.general.Identify.request", - new=asynctest.CoroutineMock( - return_value=[mock.sentinel.data, zcl_f.Status.SUCCESS] - ), + new=AsyncMock(return_value=[mock.sentinel.data, zcl_f.Status.SUCCESS]), ) @pytest.mark.parametrize("device", DEVICES) async def test_devices( diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 03c29283c20..915ace7c7f2 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -2,7 +2,6 @@ from datetime import timedelta from unittest.mock import MagicMock, call, sentinel -from asynctest import CoroutineMock, patch import pytest import zigpy.profiles.zha as zha import zigpy.types @@ -24,6 +23,7 @@ from .common import ( send_attributes_report, ) +from tests.async_mock import AsyncMock, patch from tests.common import async_fire_time_changed ON = 1 @@ -206,19 +206,19 @@ async def test_light_refresh(hass, zigpy_device_mock, zha_device_joined_restored @patch( "zigpy.zcl.clusters.lighting.Color.request", - new=CoroutineMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) @patch( "zigpy.zcl.clusters.general.Identify.request", - new=CoroutineMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) @patch( "zigpy.zcl.clusters.general.LevelControl.request", - new=CoroutineMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) @patch( "zigpy.zcl.clusters.general.OnOff.request", - new=CoroutineMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), + new=AsyncMock(return_value=[sentinel.data, zcl_f.Status.SUCCESS]), ) @pytest.mark.parametrize( "device, reporting", diff --git a/tests/components/zone/test_init.py b/tests/components/zone/test_init.py index bf92a6aa12a..994bd5e6dda 100644 --- a/tests/components/zone/test_init.py +++ b/tests/components/zone/test_init.py @@ -1,5 +1,4 @@ """Test zone component.""" -from asynctest import patch import pytest from homeassistant import setup @@ -16,6 +15,7 @@ from homeassistant.core import Context from homeassistant.exceptions import Unauthorized from homeassistant.helpers import entity_registry +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index e14162d5788..b71758469d0 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -23,12 +23,8 @@ from homeassistant.helpers.device_registry import async_get_registry as get_dev_ from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.setup import setup_component -from tests.common import ( - async_fire_time_changed, - get_test_home_assistant, - mock_coro, - mock_registry, -) +from tests.async_mock import AsyncMock +from tests.common import async_fire_time_changed, get_test_home_assistant, mock_registry from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue @@ -873,7 +869,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): @patch.object(zwave, "discovery") def test_entity_discovery(self, discovery, import_module): """Test the creation of a new entity.""" - discovery.async_load_platform.return_value = mock_coro() + discovery.async_load_platform = AsyncMock(return_value=None) mock_platform = MagicMock() import_module.return_value = mock_platform mock_device = MagicMock() @@ -935,7 +931,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): @patch.object(zwave, "discovery") def test_entity_existing_values(self, discovery, import_module): """Test the loading of already discovered values.""" - discovery.async_load_platform.return_value = mock_coro() + discovery.async_load_platform = AsyncMock(return_value=None) mock_platform = MagicMock() import_module.return_value = mock_platform mock_device = MagicMock() @@ -1003,7 +999,7 @@ class TestZWaveDeviceEntityValues(unittest.TestCase): @patch.object(zwave, "discovery") def test_entity_workaround_component(self, discovery, import_module): """Test component workaround.""" - discovery.async_load_platform.return_value = mock_coro() + discovery.async_load_platform = AsyncMock(return_value=None) mock_platform = MagicMock() import_module.return_value = mock_platform mock_device = MagicMock() diff --git a/tests/conftest.py b/tests/conftest.py index ccc16c8ef82..c7885c6125e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,6 @@ import functools import logging -from asynctest import patch import pytest import requests_mock as _requests_mock @@ -19,6 +18,8 @@ from homeassistant.exceptions import ServiceNotFound from homeassistant.setup import async_setup_component from homeassistant.util import location +from tests.async_mock import patch + pytest.register_assert_rewrite("tests.common") from tests.common import ( # noqa: E402, isort:skip diff --git a/tests/helpers/test_area_registry.py b/tests/helpers/test_area_registry.py index 87e4b4c4d03..f6a75fe3c30 100644 --- a/tests/helpers/test_area_registry.py +++ b/tests/helpers/test_area_registry.py @@ -1,12 +1,12 @@ """Tests for the Area Registry.""" import asyncio -import asynctest import pytest from homeassistant.core import callback from homeassistant.helpers import area_registry +import tests.async_mock from tests.common import flush_store, mock_area_registry @@ -166,7 +166,7 @@ async def test_loading_area_from_storage(hass, hass_storage): async def test_loading_race_condition(hass): """Test only one storage load called when concurrent loading occurred .""" - with asynctest.patch( + with tests.async_mock.patch( "homeassistant.helpers.area_registry.AreaRegistry.async_load" ) as mock_load: results = await asyncio.gather( diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index 868ff082ec3..1331cf615d1 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -1,10 +1,10 @@ """Tests for the Config Entry Flow helper.""" -from asynctest import Mock, patch import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.helpers import config_entry_flow +from tests.async_mock import Mock, patch from tests.common import ( MockConfigEntry, MockModule, diff --git a/tests/helpers/test_debounce.py b/tests/helpers/test_debounce.py index d51eb22c90d..d84b6fd7da7 100644 --- a/tests/helpers/test_debounce.py +++ b/tests/helpers/test_debounce.py @@ -1,8 +1,8 @@ """Tests for debounce.""" -from asynctest import CoroutineMock - from homeassistant.helpers import debounce +from tests.async_mock import AsyncMock + async def test_immediate_works(hass): """Test immediate works.""" @@ -12,7 +12,7 @@ async def test_immediate_works(hass): None, cooldown=0.01, immediate=True, - function=CoroutineMock(side_effect=lambda: calls.append(None)), + function=AsyncMock(side_effect=lambda: calls.append(None)), ) # Call when nothing happening @@ -57,7 +57,7 @@ async def test_not_immediate_works(hass): None, cooldown=0.01, immediate=False, - function=CoroutineMock(side_effect=lambda: calls.append(None)), + function=AsyncMock(side_effect=lambda: calls.append(None)), ) # Call when nothing happening diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index d5f762fc701..f81d20ecd66 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -2,12 +2,12 @@ import asyncio from unittest.mock import patch -import asynctest import pytest from homeassistant.core import callback from homeassistant.helpers import device_registry +import tests.async_mock from tests.common import flush_store, mock_device_registry @@ -474,7 +474,7 @@ async def test_update_remove_config_entries(hass, registry, update_events): async def test_loading_race_condition(hass): """Test only one storage load called when concurrent loading occurred .""" - with asynctest.patch( + with tests.async_mock.patch( "homeassistant.helpers.device_registry.DeviceRegistry.async_load" ) as mock_load: results = await asyncio.gather( diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 1c5224d89c3..625f21d9d9f 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -3,9 +3,7 @@ from collections import OrderedDict from datetime import timedelta import logging -from unittest.mock import Mock, patch -import asynctest import pytest import voluptuous as vol @@ -17,13 +15,13 @@ from homeassistant.helpers.entity_component import EntityComponent from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import AsyncMock, Mock, patch from tests.common import ( MockConfigEntry, MockEntity, MockModule, MockPlatform, async_fire_time_changed, - mock_coro, mock_entity_platform, mock_integration, ) @@ -82,13 +80,8 @@ async def test_setup_recovers_when_setup_raises(hass): assert platform2_setup.called -@asynctest.patch( - "homeassistant.helpers.entity_component.EntityComponent.async_setup_platform", - return_value=mock_coro(), -) -@asynctest.patch( - "homeassistant.setup.async_setup_component", return_value=mock_coro(True) -) +@patch("homeassistant.helpers.entity_component.EntityComponent.async_setup_platform",) +@patch("homeassistant.setup.async_setup_component", return_value=True) async def test_setup_does_discovery(mock_setup_component, mock_setup, hass): """Test setup for discovery.""" component = EntityComponent(_LOGGER, DOMAIN, hass) @@ -105,7 +98,7 @@ async def test_setup_does_discovery(mock_setup_component, mock_setup, hass): assert ("platform_test", {}, {"msg": "discovery_info"}) == mock_setup.call_args[0] -@asynctest.patch("homeassistant.helpers.entity_platform.async_track_time_interval") +@patch("homeassistant.helpers.entity_platform.async_track_time_interval") async def test_set_scan_interval_via_config(mock_track, hass): """Test the setting of the scan interval via configuration.""" @@ -295,7 +288,7 @@ async def test_setup_dependencies_platform(hass): async def test_setup_entry(hass): """Test setup entry calls async_setup_entry on platform.""" - mock_setup_entry = Mock(return_value=mock_coro(True)) + mock_setup_entry = AsyncMock(return_value=True) mock_entity_platform( hass, "test_domain.entry_domain", @@ -326,7 +319,7 @@ async def test_setup_entry_platform_not_exist(hass): async def test_setup_entry_fails_duplicate(hass): """Test we don't allow setting up a config entry twice.""" - mock_setup_entry = Mock(return_value=mock_coro(True)) + mock_setup_entry = AsyncMock(return_value=True) mock_entity_platform( hass, "test_domain.entry_domain", @@ -344,7 +337,7 @@ async def test_setup_entry_fails_duplicate(hass): async def test_unload_entry_resets_platform(hass): """Test unloading an entry removes all entities.""" - mock_setup_entry = Mock(return_value=mock_coro(True)) + mock_setup_entry = AsyncMock(return_value=True) mock_entity_platform( hass, "test_domain.entry_domain", @@ -380,7 +373,7 @@ async def test_update_entity(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) entity = MockEntity() entity.async_write_ha_state = Mock() - entity.async_update_ha_state = Mock(return_value=mock_coro()) + entity.async_update_ha_state = AsyncMock(return_value=None) await component.async_add_entities([entity]) # Called as part of async_add_entities diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index df247d82d5c..3f03dfece11 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -2,9 +2,7 @@ import asyncio from datetime import timedelta import logging -from unittest.mock import MagicMock, Mock, patch -import asynctest import pytest from homeassistant.const import UNIT_PERCENTAGE @@ -18,6 +16,7 @@ from homeassistant.helpers.entity_component import ( ) import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import ( MockConfigEntry, MockEntity, @@ -136,7 +135,7 @@ async def test_update_state_adds_entities_with_update_before_add_false(hass): assert not ent.update.called -@asynctest.patch("homeassistant.helpers.entity_platform.async_track_time_interval") +@patch("homeassistant.helpers.entity_platform.async_track_time_interval") async def test_set_scan_interval_via_platform(mock_track, hass): """Test the setting of the scan interval via platform.""" @@ -183,7 +182,7 @@ async def test_platform_warn_slow_setup(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) - with patch.object(hass.loop, "call_later", MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: await component.async_setup({DOMAIN: {"platform": "platform"}}) assert mock_call.called diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index cda2f1245fb..b2bfd1f48ff 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -2,13 +2,13 @@ import asyncio from unittest.mock import patch -import asynctest 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 +import tests.async_mock from tests.common import MockConfigEntry, flush_store, mock_registry YAML__OPEN_PATH = "homeassistant.util.yaml.loader.open" @@ -401,7 +401,7 @@ async def test_loading_invalid_entity_id(hass, hass_storage): async def test_loading_race_condition(hass): """Test only one storage load called when concurrent loading occurred .""" - with asynctest.patch( + with tests.async_mock.patch( "homeassistant.helpers.entity_registry.EntityRegistry.async_load" ) as mock_load: results = await asyncio.gather( diff --git a/tests/helpers/test_restore_state.py b/tests/helpers/test_restore_state.py index da2acee6625..7866662266d 100644 --- a/tests/helpers/test_restore_state.py +++ b/tests/helpers/test_restore_state.py @@ -1,8 +1,6 @@ """The tests for the Restore component.""" from datetime import datetime -from asynctest import patch - from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.core import CoreState, State from homeassistant.exceptions import HomeAssistantError @@ -16,7 +14,7 @@ from homeassistant.helpers.restore_state import ( ) from homeassistant.util import dt as dt_util -from tests.common import mock_coro +from tests.async_mock import patch async def test_caching_data(hass): @@ -193,7 +191,7 @@ async def test_dump_error(hass): with patch( "homeassistant.helpers.restore_state.Store.async_save", - return_value=mock_coro(exception=HomeAssistantError), + side_effect=HomeAssistantError, ) as mock_write_data, patch.object(hass.states, "async_all", return_value=states): await data.async_dump_states() @@ -208,7 +206,7 @@ async def test_load_error(hass): with patch( "homeassistant.helpers.storage.Store.async_load", - return_value=mock_coro(exception=HomeAssistantError), + side_effect=HomeAssistantError, ): state = await entity.async_get_last_state() diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 9d7e7751c10..c00dadc27e8 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -6,7 +6,6 @@ from datetime import timedelta import logging from unittest import mock -import asynctest import pytest import voluptuous as vol @@ -19,6 +18,7 @@ from homeassistant.helpers import config_validation as cv, script from homeassistant.helpers.event import async_call_later import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( async_capture_events, async_fire_time_changed, @@ -776,7 +776,7 @@ async def test_condition_basic(hass, script_mode): @pytest.mark.parametrize("script_mode", _BASIC_SCRIPT_MODES) -@asynctest.patch("homeassistant.helpers.script.condition.async_from_config") +@patch("homeassistant.helpers.script.condition.async_from_config") async def test_condition_created_once(async_from_config, hass, script_mode): """Test that the conditions do not get created multiple times.""" sequence = cv.SCRIPT_SCHEMA( diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 8819684e8d6..e87fd2646dd 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -3,7 +3,6 @@ from collections import OrderedDict from copy import deepcopy import unittest -from asynctest import CoroutineMock, Mock, patch import pytest import voluptuous as vol @@ -27,6 +26,7 @@ from homeassistant.helpers import ( import homeassistant.helpers.config_validation as cv from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock, Mock, patch from tests.common import ( MockEntity, get_test_home_assistant, @@ -39,7 +39,9 @@ from tests.common import ( @pytest.fixture def mock_handle_entity_call(): """Mock service platform call.""" - with patch("homeassistant.helpers.service._handle_entity_call") as mock_call: + with patch( + "homeassistant.helpers.service._handle_entity_call", return_value=None, + ) as mock_call: yield mock_call @@ -306,7 +308,7 @@ async def test_async_get_all_descriptions(hass): async def test_call_with_required_features(hass, mock_entities): """Test service calls invoked only if entity has required feautres.""" - test_service_mock = CoroutineMock(return_value=None) + test_service_mock = AsyncMock(return_value=None) await service.entity_service_call( hass, [Mock(entities=mock_entities)], @@ -321,7 +323,7 @@ async def test_call_with_required_features(hass, mock_entities): async def test_call_with_sync_func(hass, mock_entities): """Test invoking sync service calls.""" - test_service_mock = Mock() + test_service_mock = Mock(return_value=None) await service.entity_service_call( hass, [Mock(entities=mock_entities)], @@ -333,7 +335,7 @@ async def test_call_with_sync_func(hass, mock_entities): async def test_call_with_sync_attr(hass, mock_entities): """Test invoking sync service calls.""" - mock_method = mock_entities["light.kitchen"].sync_method = Mock() + mock_method = mock_entities["light.kitchen"].sync_method = Mock(return_value=None) await service.entity_service_call( hass, [Mock(entities=mock_entities)], diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py index 1966430113c..6325294033f 100644 --- a/tests/helpers/test_storage.py +++ b/tests/helpers/test_storage.py @@ -3,7 +3,6 @@ import asyncio from datetime import timedelta import json -from asynctest import Mock, patch import pytest from homeassistant.const import ( @@ -14,6 +13,7 @@ from homeassistant.core import CoreState from homeassistant.helpers import storage from homeassistant.util import dt +from tests.async_mock import Mock, patch from tests.common import async_fire_time_changed MOCK_VERSION = 1 diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 3957d2c19ee..8596b9dd7f3 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -3,14 +3,15 @@ import asyncio from os import path import pathlib -from asynctest import Mock, patch import pytest from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.generated import config_flows from homeassistant.helpers import translation from homeassistant.loader import async_get_integration -from homeassistant.setup import async_setup_component +from homeassistant.setup import async_setup_component, setup_component + +from tests.async_mock import Mock, patch @pytest.fixture @@ -142,11 +143,11 @@ async def test_get_translations_loads_config_flows(hass, mock_config_flows): integration = Mock(file_path=pathlib.Path(__file__)) integration.name = "Component 1" - with patch.object( - translation, "component_translation_path", return_value="bla.json" - ), patch.object( - translation, - "load_translations_files", + with patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), patch( + "homeassistant.helpers.translation.load_translations_files", return_value={"component1": {"hello": "world"}}, ), patch( "homeassistant.helpers.translation.async_get_integration", @@ -169,16 +170,18 @@ async def test_get_translations_while_loading_components(hass): integration.name = "Component 1" hass.config.components.add("component1") - async def mock_load_translation_files(files): + def mock_load_translation_files(files): """Mock load translation files.""" # Mimic race condition by loading a component during setup - await async_setup_component(hass, "persistent_notification", {}) + setup_component(hass, "persistent_notification", {}) return {"component1": {"hello": "world"}} - with patch.object( - translation, "component_translation_path", return_value="bla.json" - ), patch.object( - translation, "load_translations_files", side_effect=mock_load_translation_files, + with patch( + "homeassistant.helpers.translation.component_translation_path", + return_value="bla.json", + ), patch( + "homeassistant.helpers.translation.load_translations_files", + mock_load_translation_files, ), patch( "homeassistant.helpers.translation.async_get_integration", return_value=integration, @@ -229,9 +232,8 @@ async def test_translation_merging(hass, caplog): result["sensor.season"] = {"state": "bad data"} return result - with patch.object( - translation, - "load_translations_files", + with patch( + "homeassistant.helpers.translation.load_translations_files", side_effect=mock_load_translations_files, ): translations = await translation.async_get_translations(hass, "en", "state") diff --git a/tests/helpers/test_update_coordinator.py b/tests/helpers/test_update_coordinator.py index 897367619ed..8d4f6934d78 100644 --- a/tests/helpers/test_update_coordinator.py +++ b/tests/helpers/test_update_coordinator.py @@ -4,12 +4,12 @@ from datetime import timedelta import logging import aiohttp -from asynctest import CoroutineMock, Mock import pytest from homeassistant.helpers import update_coordinator from homeassistant.util.dt import utcnow +from tests.async_mock import AsyncMock, Mock from tests.common import async_fire_time_changed LOGGER = logging.getLogger(__name__) @@ -89,7 +89,7 @@ async def test_request_refresh(crd): ) async def test_refresh_known_errors(err_msg, crd, caplog): """Test raising known errors.""" - crd.update_method = CoroutineMock(side_effect=err_msg[0]) + crd.update_method = AsyncMock(side_effect=err_msg[0]) await crd.async_refresh() @@ -102,7 +102,7 @@ async def test_refresh_fail_unknown(crd, caplog): """Test raising unknown error.""" await crd.async_refresh() - crd.update_method = CoroutineMock(side_effect=ValueError) + crd.update_method = AsyncMock(side_effect=ValueError) await crd.async_refresh() diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 72f26ae33ad..a639b16893b 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -4,7 +4,6 @@ import logging import os from unittest.mock import Mock -from asynctest import patch import pytest from homeassistant import bootstrap @@ -12,6 +11,7 @@ import homeassistant.config as config_util from homeassistant.exceptions import HomeAssistantError import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( MockConfigEntry, MockModule, diff --git a/tests/test_config.py b/tests/test_config.py index b92ea0e890b..5d9fa7d8da1 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,10 +4,7 @@ from collections import OrderedDict import copy import os from unittest import mock -from unittest.mock import Mock -import asynctest -from asynctest import CoroutineMock, patch import pytest import voluptuous as vol from voluptuous import Invalid, MultipleInvalid @@ -37,6 +34,7 @@ from homeassistant.loader import async_get_integration from homeassistant.util import dt as dt_util from homeassistant.util.yaml import SECRET_YAML +from tests.async_mock import AsyncMock, Mock, patch from tests.common import get_test_config_dir, patch_yaml_files CONFIG_DIR = get_test_config_dir() @@ -98,7 +96,7 @@ async def test_ensure_config_exists_creates_config(hass): If not creates a new config file. """ - with mock.patch("builtins.print") as mock_print: + with patch("builtins.print") as mock_print: await config_util.async_ensure_config_exists(hass) assert os.path.isfile(YAML_PATH) @@ -168,7 +166,7 @@ async def test_create_default_config_returns_none_if_write_error(hass): Non existing folder returns None. """ hass.config.config_dir = os.path.join(CONFIG_DIR, "non_existing_dir/") - with mock.patch("builtins.print") as mock_print: + with patch("builtins.print") as mock_print: assert await config_util.async_create_default_config(hass) is False assert mock_print.called @@ -245,15 +243,15 @@ async def test_entity_customization(hass): assert state.attributes["hidden"] -@mock.patch("homeassistant.config.shutil") -@mock.patch("homeassistant.config.os") -@mock.patch("homeassistant.config.is_docker_env", return_value=False) +@patch("homeassistant.config.shutil") +@patch("homeassistant.config.os") +@patch("homeassistant.config.is_docker_env", return_value=False) def test_remove_lib_on_upgrade(mock_docker, mock_os, mock_shutil, hass): """Test removal of library on upgrade from before 0.50.""" ha_version = "0.49.0" mock_os.path.isdir = mock.Mock(return_value=True) mock_open = mock.mock_open() - with mock.patch("homeassistant.config.open", mock_open, create=True): + with patch("homeassistant.config.open", mock_open, create=True): opened_file = mock_open.return_value # pylint: disable=no-member opened_file.readline.return_value = ha_version @@ -267,15 +265,15 @@ def test_remove_lib_on_upgrade(mock_docker, mock_os, mock_shutil, hass): assert mock_shutil.rmtree.call_args == mock.call(hass_path) -@mock.patch("homeassistant.config.shutil") -@mock.patch("homeassistant.config.os") -@mock.patch("homeassistant.config.is_docker_env", return_value=True) +@patch("homeassistant.config.shutil") +@patch("homeassistant.config.os") +@patch("homeassistant.config.is_docker_env", return_value=True) def test_remove_lib_on_upgrade_94(mock_docker, mock_os, mock_shutil, hass): """Test removal of library on upgrade from before 0.94 and in Docker.""" ha_version = "0.93.0.dev0" mock_os.path.isdir = mock.Mock(return_value=True) mock_open = mock.mock_open() - with mock.patch("homeassistant.config.open", mock_open, create=True): + with patch("homeassistant.config.open", mock_open, create=True): opened_file = mock_open.return_value # pylint: disable=no-member opened_file.readline.return_value = ha_version @@ -294,9 +292,9 @@ def test_process_config_upgrade(hass): ha_version = "0.92.0" mock_open = mock.mock_open() - with mock.patch( - "homeassistant.config.open", mock_open, create=True - ), mock.patch.object(config_util, "__version__", "0.91.0"): + with patch("homeassistant.config.open", mock_open, create=True), patch.object( + config_util, "__version__", "0.91.0" + ): opened_file = mock_open.return_value # pylint: disable=no-member opened_file.readline.return_value = ha_version @@ -312,7 +310,7 @@ def test_config_upgrade_same_version(hass): ha_version = __version__ mock_open = mock.mock_open() - with mock.patch("homeassistant.config.open", mock_open, create=True): + with patch("homeassistant.config.open", mock_open, create=True): opened_file = mock_open.return_value # pylint: disable=no-member opened_file.readline.return_value = ha_version @@ -326,7 +324,7 @@ def test_config_upgrade_no_file(hass): """Test update of version on upgrade, with no version file.""" mock_open = mock.mock_open() mock_open.side_effect = [FileNotFoundError(), mock.DEFAULT, mock.DEFAULT] - with mock.patch("homeassistant.config.open", mock_open, create=True): + with patch("homeassistant.config.open", mock_open, create=True): opened_file = mock_open.return_value # pylint: disable=no-member config_util.process_ha_config_upgrade(hass) @@ -505,14 +503,14 @@ async def test_loading_configuration_from_packages(hass): ) -@asynctest.mock.patch("homeassistant.helpers.check_config.async_check_ha_config_file") +@patch("homeassistant.helpers.check_config.async_check_ha_config_file") async def test_check_ha_config_file_correct(mock_check, hass): """Check that restart propagates to stop.""" mock_check.return_value = check_config.HomeAssistantConfig() assert await config_util.async_check_ha_config_file(hass) is None -@asynctest.mock.patch("homeassistant.helpers.check_config.async_check_ha_config_file") +@patch("homeassistant.helpers.check_config.async_check_ha_config_file") async def test_check_ha_config_file_wrong(mock_check, hass): """Check that restart with a bad config doesn't propagate to stop.""" mock_check.return_value = check_config.HomeAssistantConfig() @@ -521,9 +519,7 @@ async def test_check_ha_config_file_wrong(mock_check, hass): assert await config_util.async_check_ha_config_file(hass) == "bad" -@asynctest.mock.patch( - "homeassistant.config.os.path.isfile", mock.Mock(return_value=True) -) +@patch("homeassistant.config.os.path.isfile", mock.Mock(return_value=True)) async def test_async_hass_config_yaml_merge(merge_log_err, hass): """Test merge during async config reload.""" config = { @@ -549,7 +545,7 @@ async def test_async_hass_config_yaml_merge(merge_log_err, hass): @pytest.fixture def merge_log_err(hass): """Patch _merge_log_error from packages.""" - with mock.patch("homeassistant.config._LOGGER.error") as logerr: + with patch("homeassistant.config._LOGGER.error") as logerr: yield logerr @@ -907,7 +903,7 @@ async def test_component_config_exceptions(hass, caplog): domain="test_domain", get_platform=Mock( return_value=Mock( - async_validate_config=CoroutineMock( + async_validate_config=AsyncMock( side_effect=ValueError("broken") ) ) diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 6d5c735b882..592bc1d4656 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -2,7 +2,6 @@ import asyncio from datetime import timedelta -from asynctest import CoroutineMock, MagicMock, patch import pytest from homeassistant import config_entries, data_entry_flow, loader @@ -11,6 +10,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.setup import async_setup_component from homeassistant.util import dt +from tests.async_mock import AsyncMock, patch from tests.common import ( MockConfigEntry, MockEntity, @@ -54,8 +54,8 @@ async def test_call_setup_entry(hass): entry = MockConfigEntry(domain="comp") entry.add_to_hass(hass) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) - mock_migrate_entry = MagicMock(return_value=mock_coro(True)) + mock_setup_entry = AsyncMock(return_value=True) + mock_migrate_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -80,8 +80,8 @@ async def test_call_async_migrate_entry(hass): entry.version = 2 entry.add_to_hass(hass) - mock_migrate_entry = MagicMock(return_value=mock_coro(True)) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_migrate_entry = AsyncMock(return_value=True) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -106,8 +106,8 @@ async def test_call_async_migrate_entry_failure_false(hass): entry.version = 2 entry.add_to_hass(hass) - mock_migrate_entry = MagicMock(return_value=mock_coro(False)) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_migrate_entry = AsyncMock(return_value=False) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -132,8 +132,8 @@ async def test_call_async_migrate_entry_failure_exception(hass): entry.version = 2 entry.add_to_hass(hass) - mock_migrate_entry = MagicMock(return_value=mock_coro(exception=Exception)) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_migrate_entry = AsyncMock(side_effect=Exception) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -158,8 +158,8 @@ async def test_call_async_migrate_entry_failure_not_bool(hass): entry.version = 2 entry.add_to_hass(hass) - mock_migrate_entry = MagicMock(return_value=mock_coro()) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_migrate_entry = AsyncMock(return_value=None) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -184,7 +184,7 @@ async def test_call_async_migrate_entry_failure_not_supported(hass): entry.version = 2 entry.add_to_hass(hass) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + 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) @@ -211,7 +211,7 @@ async def test_remove_entry(hass, manager): assert result return result - mock_remove_entry = MagicMock(side_effect=lambda *args, **kwargs: mock_coro()) + mock_remove_entry = AsyncMock(return_value=None) entity = MockEntity(unique_id="1234", name="Test Entity") @@ -283,9 +283,9 @@ async def test_remove_entry(hass, manager): async def test_remove_entry_handles_callback_error(hass, manager): """Test that exceptions in the remove callback are handled.""" - mock_setup_entry = MagicMock(return_value=mock_coro(True)) - mock_unload_entry = MagicMock(return_value=mock_coro(True)) - mock_remove_entry = MagicMock(side_effect=lambda *args, **kwargs: mock_coro()) + mock_setup_entry = AsyncMock(return_value=True) + mock_unload_entry = AsyncMock(return_value=True) + mock_remove_entry = AsyncMock(return_value=None) mock_integration( hass, MockModule( @@ -343,7 +343,7 @@ async def test_remove_entry_raises(hass, manager): async def test_remove_entry_if_not_loaded(hass, manager): """Test that we can remove an entry that is not loaded.""" - mock_unload_entry = MagicMock(return_value=mock_coro(True)) + mock_unload_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_unload_entry=mock_unload_entry)) @@ -367,7 +367,7 @@ async def test_remove_entry_if_not_loaded(hass, manager): async def test_add_entry_calls_setup_entry(hass, manager): """Test we call setup_config_entry.""" - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + 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) @@ -486,12 +486,12 @@ async def test_forward_entry_sets_up_component(hass): """Test we setup the component entry is forwarded to.""" entry = MockConfigEntry(domain="original") - mock_original_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_original_setup_entry = AsyncMock(return_value=True) mock_integration( hass, MockModule("original", async_setup_entry=mock_original_setup_entry) ) - mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_forwarded_setup_entry = AsyncMock(return_value=True) mock_integration( hass, MockModule("forwarded", async_setup_entry=mock_forwarded_setup_entry) ) @@ -505,8 +505,8 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass): """Test we do not set up entry if component setup fails.""" entry = MockConfigEntry(domain="original") - mock_setup = MagicMock(return_value=mock_coro(False)) - mock_setup_entry = MagicMock() + mock_setup = AsyncMock(return_value=False) + mock_setup_entry = AsyncMock() mock_integration( hass, MockModule( @@ -643,7 +643,7 @@ async def test_setup_raise_not_ready(hass, caplog): """Test a setup raising not ready.""" entry = MockConfigEntry(domain="test") - mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) + mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady) mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) mock_entity_platform(hass, "config_flow.test", None) @@ -659,7 +659,7 @@ async def test_setup_raise_not_ready(hass, caplog): assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY mock_setup_entry.side_effect = None - mock_setup_entry.return_value = mock_coro(True) + mock_setup_entry.return_value = True await p_setup(None) assert entry.state == config_entries.ENTRY_STATE_LOADED @@ -669,7 +669,7 @@ async def test_setup_retrying_during_unload(hass): """Test if we unload an entry that is in retry mode.""" entry = MockConfigEntry(domain="test") - mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady) + mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady) mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry)) mock_entity_platform(hass, "config_flow.test", None) @@ -721,8 +721,8 @@ async def test_entry_setup_succeed(hass, manager): entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_NOT_LOADED) entry.add_to_hass(hass) - mock_setup = MagicMock(return_value=mock_coro(True)) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_setup = AsyncMock(return_value=True) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -751,8 +751,8 @@ async def test_entry_setup_invalid_state(hass, manager, state): entry = MockConfigEntry(domain="comp", state=state) entry.add_to_hass(hass) - mock_setup = MagicMock(return_value=mock_coro(True)) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + mock_setup = AsyncMock(return_value=True) + mock_setup_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -772,7 +772,7 @@ async def test_entry_unload_succeed(hass, manager): entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_LOADED) entry.add_to_hass(hass) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_unload_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_unload_entry=async_unload_entry)) @@ -794,7 +794,7 @@ async def test_entry_unload_failed_to_load(hass, manager, state): entry = MockConfigEntry(domain="comp", state=state) entry.add_to_hass(hass) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_unload_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_unload_entry=async_unload_entry)) @@ -815,7 +815,7 @@ async def test_entry_unload_invalid_state(hass, manager, state): entry = MockConfigEntry(domain="comp", state=state) entry.add_to_hass(hass) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_unload_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_unload_entry=async_unload_entry)) @@ -831,9 +831,9 @@ async def test_entry_reload_succeed(hass, manager): entry = MockConfigEntry(domain="comp", state=config_entries.ENTRY_STATE_LOADED) entry.add_to_hass(hass) - async_setup = MagicMock(return_value=mock_coro(True)) - async_setup_entry = MagicMock(return_value=mock_coro(True)) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -866,9 +866,9 @@ async def test_entry_reload_not_loaded(hass, manager, state): entry = MockConfigEntry(domain="comp", state=state) entry.add_to_hass(hass) - async_setup = MagicMock(return_value=mock_coro(True)) - async_setup_entry = MagicMock(return_value=mock_coro(True)) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -900,9 +900,9 @@ async def test_entry_reload_error(hass, manager, state): entry = MockConfigEntry(domain="comp", state=state) entry.add_to_hass(hass) - async_setup = MagicMock(return_value=mock_coro(True)) - async_setup_entry = MagicMock(return_value=mock_coro(True)) - async_unload_entry = MagicMock(return_value=mock_coro(True)) + async_setup = AsyncMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -968,8 +968,8 @@ async def test_reload_entry_entity_registry_works(hass): domain="comp", state=config_entries.ENTRY_STATE_LOADED ) config_entry.add_to_hass(hass) - mock_setup_entry = MagicMock(return_value=mock_coro(True)) - mock_unload_entry = MagicMock(return_value=mock_coro(True)) + mock_setup_entry = AsyncMock(return_value=True) + mock_unload_entry = AsyncMock(return_value=True) mock_integration( hass, MockModule( @@ -1016,7 +1016,7 @@ async def test_reload_entry_entity_registry_works(hass): async def test_unqiue_id_persisted(hass, manager): """Test that a unique ID is stored in the config entry.""" - mock_setup_entry = MagicMock(return_value=mock_coro(True)) + 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) @@ -1052,9 +1052,9 @@ async def test_unique_id_existing_entry(hass, manager): unique_id="mock-unique-id", ).add_to_hass(hass) - async_setup_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) - async_unload_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) - async_remove_entry = MagicMock(side_effect=lambda _, _2: mock_coro(True)) + async_setup_entry = AsyncMock(return_value=True) + async_unload_entry = AsyncMock(return_value=True) + async_remove_entry = AsyncMock(return_value=True) mock_integration( hass, @@ -1205,8 +1205,7 @@ async def test_unique_id_in_progress(hass, manager): async def test_finish_flow_aborts_progress(hass, manager): """Test that when finishing a flow, we abort other flows in progress with unique ID.""" mock_integration( - hass, - MockModule("comp", async_setup_entry=MagicMock(return_value=mock_coro(True))), + hass, MockModule("comp", async_setup_entry=AsyncMock(return_value=True)), ) mock_entity_platform(hass, "config_flow.comp", None) @@ -1243,7 +1242,7 @@ async def test_finish_flow_aborts_progress(hass, manager): async def test_unique_id_ignore(hass, manager): """Test that we can ignore flows that are in progress and have a unique ID.""" - async_setup_entry = MagicMock(return_value=mock_coro(False)) + async_setup_entry = AsyncMock(return_value=False) mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) mock_entity_platform(hass, "config_flow.comp", None) @@ -1285,7 +1284,7 @@ async def test_unique_id_ignore(hass, manager): 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 = MagicMock(return_value=mock_coro(True)) + async_setup_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) mock_entity_platform(hass, "config_flow.comp", None) @@ -1329,7 +1328,7 @@ async def test_unignore_step_form(hass, manager): async def test_unignore_create_entry(hass, manager): """Test that we can ignore flows that are in progress and have a unique ID, then rediscover them.""" - async_setup_entry = MagicMock(return_value=mock_coro(True)) + async_setup_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) mock_entity_platform(hass, "config_flow.comp", None) @@ -1376,7 +1375,7 @@ async def test_unignore_create_entry(hass, manager): async def test_unignore_default_impl(hass, manager): """Test that resdicovery is a no-op by default.""" - async_setup_entry = MagicMock(return_value=mock_coro(True)) + async_setup_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) mock_entity_platform(hass, "config_flow.comp", None) @@ -1407,7 +1406,7 @@ async def test_unignore_default_impl(hass, manager): async def test_partial_flows_hidden(hass, manager): """Test that flows that don't have a cur_step and haven't finished initing are hidden.""" - async_setup_entry = MagicMock(return_value=mock_coro(True)) + async_setup_entry = AsyncMock(return_value=True) mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry)) mock_entity_platform(hass, "config_flow.comp", None) await async_setup_component(hass, "persistent_notification", {}) @@ -1476,7 +1475,7 @@ async def test_async_setup_init_entry(hass): ) return True - async_setup_entry = CoroutineMock(return_value=True) + async_setup_entry = AsyncMock(return_value=True) mock_integration( hass, MockModule( diff --git a/tests/test_core.py b/tests/test_core.py index 8b4a2ae9f84..3221bfcd39d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7,7 +7,6 @@ import logging import os from tempfile import TemporaryDirectory import unittest -from unittest.mock import MagicMock, patch import pytest import pytz @@ -35,6 +34,7 @@ from homeassistant.exceptions import InvalidEntityFormatError, InvalidStateError import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import METRIC_SYSTEM +from tests.async_mock import MagicMock, patch from tests.common import async_mock_service, get_test_home_assistant PST = pytz.timezone("America/Los_Angeles") diff --git a/tests/test_loader.py b/tests/test_loader.py index 745bb9c8c2c..eb99cb3a8ea 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,11 +1,11 @@ """Test to verify that we can load components.""" -from asynctest.mock import ANY, patch import pytest from homeassistant.components import http, hue from homeassistant.components.hue import light as hue_light import homeassistant.loader as loader +from tests.async_mock import ANY, patch from tests.common import MockModule, async_mock_service, mock_integration diff --git a/tests/test_requirements.py b/tests/test_requirements.py index e95db4e533d..f98485e8006 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -1,7 +1,6 @@ """Test requirements module.""" import os from pathlib import Path -from unittest.mock import call, patch import pytest @@ -15,12 +14,8 @@ from homeassistant.requirements import ( async_process_requirements, ) -from tests.common import ( - MockModule, - get_test_home_assistant, - mock_coro, - mock_integration, -) +from tests.async_mock import call, patch +from tests.common import MockModule, mock_integration def env_without_wheel_links(): @@ -30,58 +25,42 @@ def env_without_wheel_links(): return env -class TestRequirements: - """Test the requirements module.""" - - hass = None - backup_cache = None - - # pylint: disable=invalid-name, no-self-use - def setup_method(self, method): - """Set up the test.""" - self.hass = get_test_home_assistant() - - def teardown_method(self, method): - """Clean up.""" - self.hass.stop() - - @patch("os.path.dirname") - @patch("homeassistant.util.package.is_virtual_env", return_value=True) - @patch("homeassistant.util.package.is_docker_env", return_value=False) - @patch("homeassistant.util.package.install_package", return_value=True) - @patch.dict(os.environ, env_without_wheel_links(), clear=True) - def test_requirement_installed_in_venv( - self, mock_install, mock_denv, mock_venv, mock_dirname +async def test_requirement_installed_in_venv(hass): + """Test requirement installed in virtual environment.""" + with patch("os.path.dirname", return_value="ha_package_path"), patch( + "homeassistant.util.package.is_virtual_env", return_value=True + ), patch("homeassistant.util.package.is_docker_env", return_value=False), patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_install, patch.dict( + os.environ, env_without_wheel_links(), clear=True ): - """Test requirement installed in virtual environment.""" - mock_dirname.return_value = "ha_package_path" - self.hass.config.skip_pip = False - mock_integration(self.hass, MockModule("comp", requirements=["package==0.0.1"])) - assert setup.setup_component(self.hass, "comp", {}) - assert "comp" in self.hass.config.components + hass.config.skip_pip = False + mock_integration(hass, MockModule("comp", requirements=["package==0.0.1"])) + assert await setup.async_setup_component(hass, "comp", {}) + assert "comp" in hass.config.components assert mock_install.call_args == call( "package==0.0.1", constraints=os.path.join("ha_package_path", CONSTRAINT_FILE), no_cache_dir=False, ) - @patch("os.path.dirname") - @patch("homeassistant.util.package.is_virtual_env", return_value=False) - @patch("homeassistant.util.package.is_docker_env", return_value=False) - @patch("homeassistant.util.package.install_package", return_value=True) - @patch.dict(os.environ, env_without_wheel_links(), clear=True) - def test_requirement_installed_in_deps( - self, mock_install, mock_denv, mock_venv, mock_dirname + +async def test_requirement_installed_in_deps(hass): + """Test requirement installed in deps directory.""" + with patch("os.path.dirname", return_value="ha_package_path"), patch( + "homeassistant.util.package.is_virtual_env", return_value=False + ), patch("homeassistant.util.package.is_docker_env", return_value=False), patch( + "homeassistant.util.package.install_package", return_value=True + ) as mock_install, patch.dict( + os.environ, env_without_wheel_links(), clear=True ): - """Test requirement installed in deps directory.""" - mock_dirname.return_value = "ha_package_path" - self.hass.config.skip_pip = False - mock_integration(self.hass, MockModule("comp", requirements=["package==0.0.1"])) - assert setup.setup_component(self.hass, "comp", {}) - assert "comp" in self.hass.config.components + hass.config.skip_pip = False + mock_integration(hass, MockModule("comp", requirements=["package==0.0.1"])) + assert await setup.async_setup_component(hass, "comp", {}) + assert "comp" in hass.config.components assert mock_install.call_args == call( "package==0.0.1", - target=self.hass.config.path("deps"), + target=hass.config.path("deps"), constraints=os.path.join("ha_package_path", CONSTRAINT_FILE), no_cache_dir=False, ) @@ -239,7 +218,6 @@ async def test_discovery_requirements_ssdp(hass): ) with patch( "homeassistant.requirements.async_process_requirements", - side_effect=lambda _, _2, _3: mock_coro(), ) as mock_process: await async_get_integration_with_requirements(hass, "ssdp_comp") @@ -262,7 +240,6 @@ async def test_discovery_requirements_zeroconf(hass, partial_manifest): with patch( "homeassistant.requirements.async_process_requirements", - side_effect=lambda _, _2, _3: mock_coro(), ) as mock_process: await async_get_integration_with_requirements(hass, "comp") diff --git a/tests/test_setup.py b/tests/test_setup.py index a5e53861ef0..4197fe7370a 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -5,7 +5,6 @@ import logging import os import threading -from asynctest import Mock, patch import pytest import voluptuous as vol @@ -20,6 +19,7 @@ from homeassistant.helpers.config_validation import ( ) import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import ( MockConfigEntry, MockModule, diff --git a/tests/util/test_location.py b/tests/util/test_location.py index 968c6257f37..403e24121ad 100644 --- a/tests/util/test_location.py +++ b/tests/util/test_location.py @@ -1,10 +1,10 @@ """Test Home Assistant location util methods.""" import aiohttp -from asynctest import Mock, patch import pytest import homeassistant.util.location as location_util +from tests.async_mock import Mock, patch from tests.common import load_fixture # Paris diff --git a/tests/util/test_package.py b/tests/util/test_package.py index f7cce0e298f..c9f5183249e 100644 --- a/tests/util/test_package.py +++ b/tests/util/test_package.py @@ -5,12 +5,13 @@ import os from subprocess import PIPE import sys -from asynctest import MagicMock, call, patch import pkg_resources import pytest import homeassistant.util.package as package +from tests.async_mock import MagicMock, call, patch + RESOURCE_DIR = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "resources") ) @@ -70,13 +71,11 @@ def mock_venv(): yield mock -@asyncio.coroutine def mock_async_subprocess(): """Return an async Popen mock.""" async_popen = MagicMock() - @asyncio.coroutine - def communicate(input=None): + async def communicate(input=None): """Communicate mock.""" stdout = bytes("/deps_dir/lib_dir", "utf-8") return (stdout, None) From 6fe00497d6d851bc216521ebd61bc0c590ee3308 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Thu, 30 Apr 2020 23:03:54 +0200 Subject: [PATCH 162/511] Fix webhook imports sorting (#34988) --- homeassistant/components/webhook/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/webhook/__init__.py b/homeassistant/components/webhook/__init__.py index 04332166c60..4a90a247e75 100644 --- a/homeassistant/components/webhook/__init__.py +++ b/homeassistant/components/webhook/__init__.py @@ -6,8 +6,8 @@ from aiohttp.web import Request, Response import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.http.const import KEY_REAL_IP +from homeassistant.components.http.view import HomeAssistantView from homeassistant.const import HTTP_OK from homeassistant.core import callback from homeassistant.loader import bind_hass From 928d9ec117f34a07e67b1047c532b694380f5a2b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 1 May 2020 00:15:53 +0200 Subject: [PATCH 163/511] Fix not condition validation and entity/device extraction (#34959) --- homeassistant/helpers/condition.py | 6 +- tests/helpers/test_condition.py | 97 +++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 61704e2d23a..535de0304a0 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -536,7 +536,7 @@ async def async_validate_condition_config( ) -> ConfigType: """Validate config.""" condition = config[CONF_CONDITION] - if condition in ("and", "or"): + if condition in ("and", "not", "or"): conditions = [] for sub_cond in config["conditions"]: sub_cond = await async_validate_condition_config(hass, sub_cond) @@ -563,7 +563,7 @@ def async_extract_entities(config: ConfigType) -> Set[str]: config = to_process.popleft() condition = config[CONF_CONDITION] - if condition in ("and", "or"): + if condition in ("and", "not", "or"): to_process.extend(config["conditions"]) continue @@ -585,7 +585,7 @@ def async_extract_devices(config: ConfigType) -> Set[str]: config = to_process.popleft() condition = config[CONF_CONDITION] - if condition in ("and", "or"): + if condition in ("and", "not", "or"): to_process.extend(config["conditions"]) continue diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index cadce628c10..03c3965fad7 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,10 +1,31 @@ """Test the condition helper.""" from unittest.mock import patch +import pytest + +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition from homeassistant.util import dt +async def test_invalid_condition(hass): + """Test if invalid condition raises.""" + with pytest.raises(HomeAssistantError): + await condition.async_from_config( + hass, + { + "condition": "invalid", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature", + "state": "100", + }, + ], + }, + ) + + async def test_and_condition(hass): """Test the 'and' condition.""" test = await condition.async_from_config( @@ -261,9 +282,46 @@ async def test_extract_entities(): "entity_id": "sensor.temperature_2", "below": 110, }, + { + "condition": "not", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature_3", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature_4", + "below": 110, + }, + ], + }, + { + "condition": "or", + "conditions": [ + { + "condition": "state", + "entity_id": "sensor.temperature_5", + "state": "100", + }, + { + "condition": "numeric_state", + "entity_id": "sensor.temperature_6", + "below": 110, + }, + ], + }, ], } - ) == {"sensor.temperature", "sensor.temperature_2"} + ) == { + "sensor.temperature", + "sensor.temperature_2", + "sensor.temperature_3", + "sensor.temperature_4", + "sensor.temperature_5", + "sensor.temperature_6", + } async def test_extract_devices(): @@ -274,6 +332,41 @@ async def test_extract_devices(): "conditions": [ {"condition": "device", "device_id": "abcd", "domain": "light"}, {"condition": "device", "device_id": "qwer", "domain": "switch"}, + { + "condition": "state", + "entity_id": "sensor.not_a_device", + "state": "100", + }, + { + "condition": "not", + "conditions": [ + { + "condition": "device", + "device_id": "abcd_not", + "domain": "light", + }, + { + "condition": "device", + "device_id": "qwer_not", + "domain": "switch", + }, + ], + }, + { + "condition": "or", + "conditions": [ + { + "condition": "device", + "device_id": "abcd_or", + "domain": "light", + }, + { + "condition": "device", + "device_id": "qwer_or", + "domain": "switch", + }, + ], + }, ], } - ) == {"abcd", "qwer"} + ) == {"abcd", "qwer", "abcd_not", "qwer_not", "abcd_or", "qwer_or"} From 799d98eaf055db82265bcea18cb4f584c15ab5ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 17:49:16 -0500 Subject: [PATCH 164/511] Cleanup homekit callbacks and jobs (#34975) --- homeassistant/components/homekit/__init__.py | 2 +- .../components/homekit/accessories.py | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 479e1e4dab9..c77ec36ccf3 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -494,7 +494,7 @@ class HomeKit: self.status = STATUS_STOPPED _LOGGER.debug("Driver stop") - self.hass.async_add_executor_job(self.driver.stop) + self.hass.add_job(self.driver.stop) @callback def _async_configure_linked_battery_sensors(self, ent_reg, device_lookup, state): diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index ab3d1ae8507..c3b42f0b6dc 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -165,8 +165,10 @@ class HomeAccessory(Accessory): Run inside the Home Assistant event loop. """ state = self.hass.states.get(self.entity_id) - self.hass.async_add_job(self.update_state_callback, None, None, state) - async_track_state_change(self.hass, self.entity_id, self.update_state_callback) + await self.async_update_state_callback(None, None, state) + async_track_state_change( + self.hass, self.entity_id, self.async_update_state_callback + ) battery_charging_state = None battery_state = None @@ -179,7 +181,7 @@ class HomeAccessory(Accessory): ATTR_BATTERY_CHARGING ) async_track_state_change( - self.hass, self.linked_battery_sensor, self.update_linked_battery + self.hass, self.linked_battery_sensor, self.async_update_linked_battery ) else: battery_state = state.attributes.get(ATTR_BATTERY_LEVEL) @@ -191,7 +193,7 @@ class HomeAccessory(Accessory): async_track_state_change( self.hass, self.linked_battery_charging_sensor, - self.update_linked_battery_charging, + self.async_update_linked_battery_charging, ) elif battery_charging_state is None: battery_charging_state = state.attributes.get(ATTR_BATTERY_CHARGING) @@ -201,8 +203,9 @@ class HomeAccessory(Accessory): self.update_battery, battery_state, battery_charging_state ) - @ha_callback - def update_state_callback(self, entity_id=None, old_state=None, new_state=None): + async def async_update_state_callback( + self, entity_id=None, old_state=None, new_state=None + ): """Handle state change listener callback.""" _LOGGER.debug("New_state: %s", new_state) if new_state is None: @@ -220,28 +223,28 @@ class HomeAccessory(Accessory): ): battery_charging_state = new_state.attributes.get(ATTR_BATTERY_CHARGING) if battery_state is not None or battery_charging_state is not None: - self.hass.async_add_executor_job( + await self.hass.async_add_executor_job( self.update_battery, battery_state, battery_charging_state ) - self.hass.async_add_executor_job(self.update_state, new_state) + await self.hass.async_add_executor_job(self.update_state, new_state) - @ha_callback - def update_linked_battery(self, entity_id=None, old_state=None, new_state=None): + async def async_update_linked_battery( + self, entity_id=None, old_state=None, new_state=None + ): """Handle linked battery sensor state change listener callback.""" if self.linked_battery_charging_sensor: battery_charging_state = None else: battery_charging_state = new_state.attributes.get(ATTR_BATTERY_CHARGING) - self.hass.async_add_executor_job( + await self.hass.async_add_executor_job( self.update_battery, new_state.state, battery_charging_state, ) - @ha_callback - def update_linked_battery_charging( + async def async_update_linked_battery_charging( self, entity_id=None, old_state=None, new_state=None ): """Handle linked battery charging sensor state change listener callback.""" - self.hass.async_add_executor_job( + await self.hass.async_add_executor_job( self.update_battery, None, new_state.state == STATE_ON ) From ba7391528fa15a0323a9b2f47d98134e7d463bd9 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 30 Apr 2020 18:24:47 -0500 Subject: [PATCH 165/511] Add unique id to esphome config flow (#34753) --- .../components/esphome/config_flow.py | 72 +++--- homeassistant/components/esphome/strings.json | 5 +- .../components/esphome/translations/en.json | 5 +- tests/components/esphome/test_config_flow.py | 213 ++++++++++++------ 4 files changed, 192 insertions(+), 103 deletions(-) diff --git a/homeassistant/components/esphome/config_flow.py b/homeassistant/components/esphome/config_flow.py index 17d3ed5f659..cb9b7958efa 100644 --- a/homeassistant/components/esphome/config_flow.py +++ b/homeassistant/components/esphome/config_flow.py @@ -5,18 +5,21 @@ from typing import Optional from aioesphomeapi import APIClient, APIConnectionError import voluptuous as vol -from homeassistant import config_entries, core -from homeassistant.helpers.typing import ConfigType +from homeassistant.config_entries import CONN_CLASS_LOCAL_PUSH, ConfigFlow +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT +from homeassistant.core import callback +from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .entry_data import DATA_KEY, RuntimeEntryData +DOMAIN = "esphome" -@config_entries.HANDLERS.register("esphome") -class EsphomeFlowHandler(config_entries.ConfigFlow): + +class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a esphome config flow.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + CONNECTION_CLASS = CONN_CLASS_LOCAL_PUSH def __init__(self): """Initialize flow.""" @@ -32,8 +35,8 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): return await self._async_authenticate_or_add(user_input) fields = OrderedDict() - fields[vol.Required("host", default=self._host or vol.UNDEFINED)] = str - fields[vol.Optional("port", default=self._port or 6053)] = int + fields[vol.Required(CONF_HOST, default=self._host or vol.UNDEFINED)] = str + fields[vol.Optional(CONF_PORT, default=self._port or 6053)] = int errors = {} if error is not None: @@ -46,19 +49,19 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): @property def _name(self): # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - return self.context.get("name") + return self.context.get(CONF_NAME) @_name.setter def _name(self, value): # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context["name"] = value + self.context[CONF_NAME] = value self.context["title_placeholders"] = {"name": self._name} def _set_user_input(self, user_input): if user_input is None: return - self._host = user_input["host"] - self._port = user_input["port"] + self._host = user_input[CONF_HOST] + self._port = user_input[CONF_PORT] async def _async_authenticate_or_add(self, user_input): self._set_user_input(user_input) @@ -81,56 +84,69 @@ class EsphomeFlowHandler(config_entries.ConfigFlow): step_id="discovery_confirm", description_placeholders={"name": self._name} ) - async def async_step_zeroconf(self, user_input: ConfigType): + async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): """Handle zeroconf discovery.""" # Hostname is format: livingroom.local. - local_name = user_input["hostname"][:-1] + local_name = discovery_info["hostname"][:-1] node_name = local_name[: -len(".local")] - address = user_input["properties"].get("address", local_name) + address = discovery_info["properties"].get("address", local_name) # Check if already configured + await self.async_set_unique_id(node_name) + self._abort_if_unique_id_configured( + updates={CONF_HOST: discovery_info[CONF_HOST]} + ) + for entry in self._async_current_entries(): already_configured = False - if entry.data["host"] == address: - # Is this address already configured? + + if ( + entry.data[CONF_HOST] == address + or entry.data[CONF_HOST] == discovery_info[CONF_HOST] + ): + # Is this address or IP address already configured? already_configured = True elif entry.entry_id in self.hass.data.get(DATA_KEY, {}): # Does a config entry with this name already exist? data: RuntimeEntryData = self.hass.data[DATA_KEY][entry.entry_id] + # Node names are unique in the network if data.device_info is not None: already_configured = data.device_info.name == node_name if already_configured: + # Backwards compat, we update old entries + if not entry.unique_id: + self.hass.config_entries.async_update_entry( + entry, + data={**entry.data, CONF_HOST: discovery_info[CONF_HOST]}, + unique_id=node_name, + ) + return self.async_abort(reason="already_configured") - self._host = address - self._port = user_input["port"] + self._host = discovery_info[CONF_HOST] + self._port = discovery_info[CONF_PORT] self._name = node_name - # Check if flow for this device already in progress - for flow in self._async_in_progress(): - if flow["context"].get("name") == node_name: - return self.async_abort(reason="already_configured") - return await self.async_step_discovery_confirm() - @core.callback + @callback def _async_get_entry(self): return self.async_create_entry( title=self._name, data={ - "host": self._host, - "port": self._port, + CONF_HOST: self._host, + CONF_PORT: self._port, # The API uses protobuf, so empty string denotes absence - "password": self._password or "", + CONF_PASSWORD: self._password or "", }, ) async def async_step_authenticate(self, user_input=None, error=None): """Handle getting password for authentication.""" if user_input is not None: - self._password = user_input["password"] + self._password = user_input[CONF_PASSWORD] error = await self.try_login() if error: return await self.async_step_authenticate(error=error) diff --git a/homeassistant/components/esphome/strings.json b/homeassistant/components/esphome/strings.json index 0c2f0fee8d9..e5eeb150d89 100644 --- a/homeassistant/components/esphome/strings.json +++ b/homeassistant/components/esphome/strings.json @@ -1,6 +1,9 @@ { "config": { - "abort": { "already_configured": "ESP is already configured" }, + "abort": { + "already_configured": "ESP is already configured", + "already_in_progress": "ESP configuration is already in progress" + }, "error": { "resolve_error": "Can't resolve address of the ESP. If this error persists, please set a static IP address: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips", "connection_error": "Can't connect to ESP. Please make sure your YAML file contains an 'api:' line.", diff --git a/homeassistant/components/esphome/translations/en.json b/homeassistant/components/esphome/translations/en.json index b52dbc5a3f1..3f695e6f183 100644 --- a/homeassistant/components/esphome/translations/en.json +++ b/homeassistant/components/esphome/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP is already configured" + "already_configured": "ESP is already configured", + "already_in_progress": "ESP configuration is already in progress" }, "error": { "connection_error": "Can't connect to ESP. Please make sure your YAML file contains an 'api:' line.", @@ -31,4 +32,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/components/esphome/test_config_flow.py b/tests/components/esphome/test_config_flow.py index b55621226fa..50aaa989028 100644 --- a/tests/components/esphome/test_config_flow.py +++ b/tests/components/esphome/test_config_flow.py @@ -3,7 +3,13 @@ from collections import namedtuple import pytest -from homeassistant.components.esphome import DATA_KEY, config_flow +from homeassistant.components.esphome import DATA_KEY +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) from tests.async_mock import AsyncMock, MagicMock, patch from tests.common import MockConfigEntry @@ -40,26 +46,27 @@ def mock_api_connection_error(): yield mock_error -def _setup_flow_handler(hass): - flow = config_flow.EsphomeFlowHandler() - flow.hass = hass - flow.context = {} - return flow - - async def test_user_connection_works(hass, mock_client): """Test we can finish a config flow.""" - flow = _setup_flow_handler(hass) - result = await flow.async_step_user(user_input=None) - assert result["type"] == "form" + result = await hass.config_entries.flow.async_init( + "esphome", context={"source": "user"}, data=None, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test")) - result = await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 80}) + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": "user"}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 80}, + ) - assert result["type"] == "create_entry" - assert result["data"] == {"host": "127.0.0.1", "port": 80, "password": ""} + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == {CONF_HOST: "127.0.0.1", CONF_PORT: 80, CONF_PASSWORD: ""} assert result["title"] == "test" + assert len(mock_client.connect.mock_calls) == 1 assert len(mock_client.device_info.mock_calls) == 1 assert len(mock_client.disconnect.mock_calls) == 1 @@ -70,8 +77,6 @@ async def test_user_connection_works(hass, mock_client): async def test_user_resolve_error(hass, mock_api_connection_error, mock_client): """Test user step with IP resolve error.""" - flow = _setup_flow_handler(hass) - await flow.async_step_user(user_input=None) class MockResolveError(mock_api_connection_error): """Create an exception with a specific error message.""" @@ -85,13 +90,16 @@ async def test_user_resolve_error(hass, mock_api_connection_error, mock_client): new_callable=lambda: MockResolveError, ) as exc: mock_client.device_info.side_effect = exc - result = await flow.async_step_user( - user_input={"host": "127.0.0.1", "port": 6053} + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": "user"}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "resolve_error"} + assert len(mock_client.connect.mock_calls) == 1 assert len(mock_client.device_info.mock_calls) == 1 assert len(mock_client.disconnect.mock_calls) == 1 @@ -99,16 +107,18 @@ async def test_user_resolve_error(hass, mock_api_connection_error, mock_client): async def test_user_connection_error(hass, mock_api_connection_error, mock_client): """Test user step with connection error.""" - flow = _setup_flow_handler(hass) - await flow.async_step_user(user_input=None) - mock_client.device_info.side_effect = mock_api_connection_error - result = await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 6053}) + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": "user"}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "user" assert result["errors"] == {"base": "connection_error"} + assert len(mock_client.connect.mock_calls) == 1 assert len(mock_client.device_info.mock_calls) == 1 assert len(mock_client.disconnect.mock_calls) == 1 @@ -116,125 +126,159 @@ async def test_user_connection_error(hass, mock_api_connection_error, mock_clien async def test_user_with_password(hass, mock_client): """Test user step with password.""" - flow = _setup_flow_handler(hass) - await flow.async_step_user(user_input=None) - mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(True, "test")) - result = await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 6053}) + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": "user"}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "authenticate" - result = await flow.async_step_authenticate(user_input={"password": "password1"}) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_PASSWORD: "password1"} + ) - assert result["type"] == "create_entry" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["data"] == { - "host": "127.0.0.1", - "port": 6053, - "password": "password1", + CONF_HOST: "127.0.0.1", + CONF_PORT: 6053, + CONF_PASSWORD: "password1", } assert mock_client.password == "password1" async def test_user_invalid_password(hass, mock_api_connection_error, mock_client): """Test user step with invalid password.""" - flow = _setup_flow_handler(hass) - await flow.async_step_user(user_input=None) - mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(True, "test")) - await flow.async_step_user(user_input={"host": "127.0.0.1", "port": 6053}) - mock_client.connect.side_effect = mock_api_connection_error - result = await flow.async_step_authenticate(user_input={"password": "invalid"}) + result = await hass.config_entries.flow.async_init( + "esphome", + context={"source": "user"}, + data={CONF_HOST: "127.0.0.1", CONF_PORT: 6053}, + ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "authenticate" + + mock_client.connect.side_effect = mock_api_connection_error + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={CONF_PASSWORD: "invalid"} + ) + + assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "authenticate" assert result["errors"] == {"base": "invalid_password"} async def test_discovery_initiation(hass, mock_client): """Test discovery importing works.""" - flow = _setup_flow_handler(hass) + mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test8266")) + service_info = { "host": "192.168.43.183", "port": 6053, "hostname": "test8266.local.", "properties": {}, } + flow = await hass.config_entries.flow.async_init( + "esphome", context={"source": "zeroconf"}, data=service_info + ) - mock_client.device_info = AsyncMock(return_value=MockDeviceInfo(False, "test8266")) + result = await hass.config_entries.flow.async_configure( + flow["flow_id"], user_input={} + ) - result = await flow.async_step_zeroconf(user_input=service_info) - assert result["type"] == "form" - assert result["step_id"] == "discovery_confirm" - assert result["description_placeholders"]["name"] == "test8266" - assert flow.context["title_placeholders"]["name"] == "test8266" - - result = await flow.async_step_discovery_confirm(user_input={}) - assert result["type"] == "create_entry" + assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == "test8266" - assert result["data"]["host"] == "test8266.local" - assert result["data"]["port"] == 6053 + assert result["data"][CONF_HOST] == "192.168.43.183" + assert result["data"][CONF_PORT] == 6053 + + assert result["result"] + assert result["result"].unique_id == "test8266" async def test_discovery_already_configured_hostname(hass, mock_client): """Test discovery aborts if already configured via hostname.""" - MockConfigEntry( - domain="esphome", data={"host": "test8266.local", "port": 6053, "password": ""} - ).add_to_hass(hass) + entry = MockConfigEntry( + domain="esphome", + data={CONF_HOST: "test8266.local", CONF_PORT: 6053, CONF_PASSWORD: ""}, + ) + + entry.add_to_hass(hass) - flow = _setup_flow_handler(hass) service_info = { "host": "192.168.43.183", "port": 6053, "hostname": "test8266.local.", "properties": {}, } - result = await flow.async_step_zeroconf(user_input=service_info) - assert result["type"] == "abort" + result = await hass.config_entries.flow.async_init( + "esphome", context={"source": "zeroconf"}, data=service_info + ) + + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + assert entry.unique_id == "test8266" + async def test_discovery_already_configured_ip(hass, mock_client): """Test discovery aborts if already configured via static IP.""" - MockConfigEntry( - domain="esphome", data={"host": "192.168.43.183", "port": 6053, "password": ""} - ).add_to_hass(hass) + entry = MockConfigEntry( + domain="esphome", + data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, + ) + + entry.add_to_hass(hass) - flow = _setup_flow_handler(hass) service_info = { "host": "192.168.43.183", "port": 6053, "hostname": "test8266.local.", "properties": {"address": "192.168.43.183"}, } - result = await flow.async_step_zeroconf(user_input=service_info) - assert result["type"] == "abort" + result = await hass.config_entries.flow.async_init( + "esphome", context={"source": "zeroconf"}, data=service_info + ) + + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + assert entry.unique_id == "test8266" + async def test_discovery_already_configured_name(hass, mock_client): """Test discovery aborts if already configured via name.""" entry = MockConfigEntry( - domain="esphome", data={"host": "192.168.43.183", "port": 6053, "password": ""} + domain="esphome", + data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, ) entry.add_to_hass(hass) + mock_entry_data = MagicMock() mock_entry_data.device_info.name = "test8266" hass.data[DATA_KEY] = {entry.entry_id: mock_entry_data} - flow = _setup_flow_handler(hass) service_info = { - "host": "192.168.43.183", + "host": "192.168.43.184", "port": 6053, "hostname": "test8266.local.", "properties": {"address": "test8266.local"}, } - result = await flow.async_step_zeroconf(user_input=service_info) - assert result["type"] == "abort" + result = await hass.config_entries.flow.async_init( + "esphome", context={"source": "zeroconf"}, data=service_info + ) + + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + assert entry.unique_id == "test8266" + assert entry.data[CONF_HOST] == "192.168.43.184" + async def test_discovery_duplicate_data(hass, mock_client): """Test discovery aborts if same mDNS packet arrives.""" @@ -250,11 +294,36 @@ async def test_discovery_duplicate_data(hass, mock_client): result = await hass.config_entries.flow.async_init( "esphome", data=service_info, context={"source": "zeroconf"} ) - assert result["type"] == "form" + assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "discovery_confirm" result = await hass.config_entries.flow.async_init( "esphome", data=service_info, context={"source": "zeroconf"} ) - assert result["type"] == "abort" + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_in_progress" + + +async def test_discovery_updates_unique_id(hass, mock_client): + """Test a duplicate discovery host aborts and updates existing entry.""" + entry = MockConfigEntry( + domain="esphome", + data={CONF_HOST: "192.168.43.183", CONF_PORT: 6053, CONF_PASSWORD: ""}, + ) + + entry.add_to_hass(hass) + + service_info = { + "host": "192.168.43.183", + "port": 6053, + "hostname": "test8266.local.", + "properties": {"address": "test8266.local"}, + } + result = await hass.config_entries.flow.async_init( + "esphome", context={"source": "zeroconf"}, data=service_info + ) + + assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + assert entry.unique_id == "test8266" From 76f392476be4db41f787712310602575a7191f8a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 30 Apr 2020 16:31:00 -0700 Subject: [PATCH 166/511] Use a future for mock coro (#34989) --- tests/common.py | 62 ++----------------- tests/components/alexa/test_smart_home.py | 6 +- tests/components/almond/test_init.py | 14 ++--- .../components/asuswrt/test_device_tracker.py | 23 +++---- tests/components/cast/test_init.py | 15 +++-- tests/components/cloud/__init__.py | 5 +- tests/components/cloud/test_alexa_config.py | 2 +- tests/components/cloud/test_client.py | 9 +-- tests/components/cloud/test_init.py | 29 ++++----- .../components/config/test_config_entries.py | 23 ++++--- tests/components/darksky/test_sensor.py | 11 ++-- .../components/emulated_roku/test_binding.py | 4 +- tests/components/emulated_roku/test_init.py | 10 +-- tests/components/folder_watcher/test_init.py | 36 +++++------ tests/components/http/test_view.py | 8 +-- tests/components/iqvia/test_config_flow.py | 17 ++--- tests/components/melissa/test_climate.py | 15 ++--- tests/components/melissa/test_init.py | 15 +++-- tests/components/mqtt/test_config_flow.py | 6 +- tests/components/reddit/test_sensor.py | 12 ++-- .../components/seventeentrack/test_sensor.py | 11 +--- tests/components/tplink/test_init.py | 14 ++--- tests/components/twilio/test_init.py | 41 ++++++------ tests/components/updater/test_init.py | 9 +-- tests/components/wake_on_lan/test_init.py | 7 +-- .../test_yandex_transport_sensor.py | 12 ++-- 26 files changed, 154 insertions(+), 262 deletions(-) diff --git a/tests/common.py b/tests/common.py index b76fa23bbf2..d6ae25adb5e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -744,16 +744,12 @@ def patch_yaml_files(files_dict, endswith=True): def mock_coro(return_value=None, exception=None): """Return a coro that returns a value or raise an exception.""" - return mock_coro_func(return_value, exception)() - - -def mock_coro_func(return_value=None, exception=None): - """Return a method to create a coro function that returns a value.""" - - if exception: - return AsyncMock(side_effect=exception) - - return AsyncMock(return_value=return_value) + fut = asyncio.Future() + if exception is not None: + fut.set_exception(exception) + else: + fut.set_result(return_value) + return fut @contextmanager @@ -838,52 +834,6 @@ def mock_restore_cache(hass, states): hass.data[key] = hass.async_create_task(get_restore_state_data()) -class MockDependency: - """Decorator to mock install a dependency.""" - - def __init__(self, root, *args): - """Initialize decorator.""" - self.root = root - self.submodules = args - - def __enter__(self): - """Start mocking.""" - - def resolve(mock, path): - """Resolve a mock.""" - if not path: - return mock - - return resolve(getattr(mock, path[0]), path[1:]) - - base = MagicMock() - to_mock = { - f"{self.root}.{tom}": resolve(base, tom.split(".")) - for tom in self.submodules - } - to_mock[self.root] = base - - self.patcher = patch.dict("sys.modules", to_mock) - self.patcher.start() - return base - - def __exit__(self, *exc): - """Stop mocking.""" - self.patcher.stop() - return False - - def __call__(self, func): - """Apply decorator.""" - - def run_mocked(*args, **kwargs): - """Run with mocked dependencies.""" - with self as base: - args = list(args) + [base] - func(*args, **kwargs) - - return run_mocked - - class MockEntity(entity.Entity): """Mock Entity class.""" diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 1bef3a5ae12..91864027873 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -1,5 +1,4 @@ """Test for smart home alexa support.""" -from unittest.mock import patch import pytest @@ -39,7 +38,8 @@ from . import ( reported_properties, ) -from tests.common import async_mock_service, mock_coro +from tests.async_mock import patch +from tests.common import async_mock_service @pytest.fixture @@ -3831,7 +3831,7 @@ async def test_initialize_camera_stream(hass, mock_camera, mock_stream): with patch( "homeassistant.components.demo.camera.DemoCamera.stream_source", - return_value=mock_coro("rtsp://example.local"), + return_value="rtsp://example.local", ), patch( "homeassistant.helpers.network.async_get_external_url", return_value="https://mycamerastream.test", diff --git a/tests/components/almond/test_init.py b/tests/components/almond/test_init.py index d5b8deefd5e..56286b9186c 100644 --- a/tests/components/almond/test_init.py +++ b/tests/components/almond/test_init.py @@ -1,6 +1,5 @@ """Tests for Almond set up.""" from time import time -from unittest.mock import patch import pytest @@ -10,7 +9,8 @@ from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow -from tests.common import MockConfigEntry, async_fire_time_changed, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry, async_fire_time_changed @pytest.fixture(autouse=True) @@ -34,7 +34,6 @@ async def test_set_up_oauth_remote_url(hass, aioclient_mock): with patch( "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - return_value=mock_coro(), ): assert await async_setup_component(hass, "almond", {}) @@ -43,9 +42,7 @@ async def test_set_up_oauth_remote_url(hass, aioclient_mock): with patch("homeassistant.components.almond.ALMOND_SETUP_DELAY", 0), patch( "homeassistant.helpers.network.async_get_external_url", return_value="https://example.nabu.casa", - ), patch( - "pyalmond.WebAlmondAPI.async_create_device", return_value=mock_coro() - ) as mock_create_device: + ), patch("pyalmond.WebAlmondAPI.async_create_device") as mock_create_device: hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() async_fire_time_changed(hass, utcnow()) @@ -69,7 +66,6 @@ async def test_set_up_oauth_no_external_url(hass, aioclient_mock): with patch( "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", - return_value=mock_coro(), ), patch("pyalmond.WebAlmondAPI.async_create_device") as mock_create_device: assert await async_setup_component(hass, "almond", {}) @@ -104,9 +100,7 @@ async def test_set_up_local(hass, aioclient_mock): ) entry.add_to_hass(hass) - with patch( - "pyalmond.WebAlmondAPI.async_create_device", return_value=mock_coro() - ) as mock_create_device: + with patch("pyalmond.WebAlmondAPI.async_create_device") as mock_create_device: assert await async_setup_component(hass, "almond", {}) assert entry.state == config_entries.ENTRY_STATE_LOADED diff --git a/tests/components/asuswrt/test_device_tracker.py b/tests/components/asuswrt/test_device_tracker.py index 3954808aa37..ed733b54d25 100644 --- a/tests/components/asuswrt/test_device_tracker.py +++ b/tests/components/asuswrt/test_device_tracker.py @@ -1,5 +1,4 @@ """The tests for the ASUSWRT device tracker platform.""" -from unittest.mock import patch from homeassistant.components.asuswrt import ( CONF_DNSMASQ, @@ -10,13 +9,13 @@ from homeassistant.components.asuswrt import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component -from tests.common import mock_coro_func +from tests.async_mock import AsyncMock, patch async def test_password_or_pub_key_required(hass): """Test creating an AsusWRT scanner without a pass or pubkey.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func() + AsusWrt().connection.async_connect = AsyncMock() AsusWrt().is_connected = False result = await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_HOST: "fake_host", CONF_USERNAME: "fake_user"}} @@ -27,7 +26,7 @@ async def test_password_or_pub_key_required(hass): async def test_network_unreachable(hass): """Test creating an AsusWRT scanner without a pass or pubkey.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func(exception=OSError) + AsusWrt().connection.async_connect = AsyncMock(side_effect=OSError) AsusWrt().is_connected = False result = await async_setup_component( hass, DOMAIN, {DOMAIN: {CONF_HOST: "fake_host", CONF_USERNAME: "fake_user"}} @@ -39,10 +38,8 @@ async def test_network_unreachable(hass): async def test_get_scanner_with_password_no_pubkey(hass): """Test creating an AsusWRT scanner with a password and no pubkey.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func() - AsusWrt().connection.async_get_connected_devices = mock_coro_func( - return_value={} - ) + AsusWrt().connection.async_connect = AsyncMock() + AsusWrt().connection.async_get_connected_devices = AsyncMock(return_value={}) result = await async_setup_component( hass, DOMAIN, @@ -62,7 +59,7 @@ async def test_get_scanner_with_password_no_pubkey(hass): async def test_specify_non_directory_path_for_dnsmasq(hass): """Test creating an AsusWRT scanner with a dnsmasq location which is not a valid directory.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func() + AsusWrt().connection.async_connect = AsyncMock() AsusWrt().is_connected = False result = await async_setup_component( hass, @@ -82,10 +79,8 @@ async def test_specify_non_directory_path_for_dnsmasq(hass): async def test_interface(hass): """Test creating an AsusWRT scanner using interface eth1.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func() - AsusWrt().connection.async_get_connected_devices = mock_coro_func( - return_value={} - ) + AsusWrt().connection.async_connect = AsyncMock() + AsusWrt().connection.async_get_connected_devices = AsyncMock(return_value={}) result = await async_setup_component( hass, DOMAIN, @@ -106,7 +101,7 @@ async def test_interface(hass): async def test_no_interface(hass): """Test creating an AsusWRT scanner using no interface.""" with patch("homeassistant.components.asuswrt.AsusWrt") as AsusWrt: - AsusWrt().connection.async_connect = mock_coro_func() + AsusWrt().connection.async_connect = AsyncMock() AsusWrt().is_connected = False result = await async_setup_component( hass, diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 6971c071353..8f194668e56 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -1,19 +1,18 @@ """Tests for the Cast config flow.""" -from unittest.mock import patch from homeassistant import config_entries, data_entry_flow from homeassistant.components import cast from homeassistant.setup import async_setup_component -from tests.common import MockDependency, mock_coro +from tests.async_mock import patch async def test_creating_entry_sets_up_media_player(hass): """Test setting up Cast loads the media player.""" with patch( "homeassistant.components.cast.media_player.async_setup_entry", - return_value=mock_coro(True), - ) as mock_setup, MockDependency("pychromecast", "discovery"), patch( + return_value=True, + ) as mock_setup, patch( "pychromecast.discovery.discover_chromecasts", return_value=True ): result = await hass.config_entries.flow.async_init( @@ -34,8 +33,8 @@ async def test_creating_entry_sets_up_media_player(hass): async def test_configuring_cast_creates_entry(hass): """Test that specifying config will create an entry.""" with patch( - "homeassistant.components.cast.async_setup_entry", return_value=mock_coro(True) - ) as mock_setup, MockDependency("pychromecast", "discovery"), patch( + "homeassistant.components.cast.async_setup_entry", return_value=True + ) as mock_setup, patch( "pychromecast.discovery.discover_chromecasts", return_value=True ): await async_setup_component( @@ -49,8 +48,8 @@ async def test_configuring_cast_creates_entry(hass): async def test_not_configuring_cast_not_creates_entry(hass): """Test that no config will not create an entry.""" with patch( - "homeassistant.components.cast.async_setup_entry", return_value=mock_coro(True) - ) as mock_setup, MockDependency("pychromecast", "discovery"), patch( + "homeassistant.components.cast.async_setup_entry", return_value=True + ) as mock_setup, patch( "pychromecast.discovery.discover_chromecasts", return_value=True ): await async_setup_component(hass, cast.DOMAIN, {}) diff --git a/tests/components/cloud/__init__.py b/tests/components/cloud/__init__.py index 571b73e8d09..da7c6ff13d0 100644 --- a/tests/components/cloud/__init__.py +++ b/tests/components/cloud/__init__.py @@ -1,18 +1,17 @@ """Tests for the cloud component.""" -from unittest.mock import patch from homeassistant.components import cloud from homeassistant.components.cloud import const from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import AsyncMock, patch async def mock_cloud(hass, config=None): """Mock cloud.""" assert await async_setup_component(hass, cloud.DOMAIN, {"cloud": config or {}}) cloud_inst = hass.data["cloud"] - with patch("hass_nabucasa.Cloud.run_executor", return_value=mock_coro()): + with patch("hass_nabucasa.Cloud.run_executor", AsyncMock(return_value=None)): await cloud_inst.start() diff --git a/tests/components/cloud/test_alexa_config.py b/tests/components/cloud/test_alexa_config.py index c239636e1d3..b064a5c9605 100644 --- a/tests/components/cloud/test_alexa_config.py +++ b/tests/components/cloud/test_alexa_config.py @@ -89,7 +89,7 @@ def patch_sync_helper(): to_update = [] to_remove = [] - async def sync_helper(to_upd, to_rem): + def sync_helper(to_upd, to_rem): to_update.extend([ent_id for ent_id in to_upd if ent_id not in to_update]) to_remove.extend([ent_id for ent_id in to_rem if ent_id not in to_remove]) return True diff --git a/tests/components/cloud/test_client.py b/tests/components/cloud/test_client.py index 0ce79daab15..21eb59ddc03 100644 --- a/tests/components/cloud/test_client.py +++ b/tests/components/cloud/test_client.py @@ -1,6 +1,4 @@ """Test the cloud.iot module.""" -from unittest.mock import MagicMock, patch - from aiohttp import web import pytest @@ -12,8 +10,7 @@ from homeassistant.setup import async_setup_component from . import mock_cloud, mock_cloud_prefs -from tests.async_mock import AsyncMock -from tests.common import mock_coro +from tests.async_mock import AsyncMock, MagicMock, patch from tests.components.alexa import test_smart_home as test_alexa @@ -131,7 +128,7 @@ async def test_handler_google_actions_disabled(hass, mock_cloud_fixture): """Test handler Google Actions when user has disabled it.""" mock_cloud_fixture._prefs[PREF_ENABLE_GOOGLE] = False - with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()): + with patch("hass_nabucasa.Cloud.start"): assert await async_setup_component(hass, "cloud", {}) reqid = "5711642932632160983" @@ -146,7 +143,7 @@ async def test_handler_google_actions_disabled(hass, mock_cloud_fixture): async def test_webhook_msg(hass): """Test webhook msg.""" - with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()): + with patch("hass_nabucasa.Cloud.start"): setup = await async_setup_component(hass, "cloud", {"cloud": {}}) assert setup cloud = hass.data["cloud"] diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 10a7bc38c05..e174b080102 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -1,5 +1,4 @@ """Test the cloud component.""" -from unittest.mock import patch import pytest @@ -11,12 +10,12 @@ from homeassistant.core import Context from homeassistant.exceptions import Unauthorized from homeassistant.setup import async_setup_component -from tests.common import mock_coro +from tests.async_mock import patch async def test_constructor_loads_info_from_config(hass): """Test non-dev mode loads info from SERVERS constant.""" - with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()): + with patch("hass_nabucasa.Cloud.start"): result = await async_setup_component( hass, "cloud", @@ -63,17 +62,13 @@ async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): assert hass.services.has_service(DOMAIN, "remote_connect") assert hass.services.has_service(DOMAIN, "remote_disconnect") - with patch( - "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro() - ) as mock_connect: + with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect: await hass.services.async_call(DOMAIN, "remote_connect", blocking=True) assert mock_connect.called assert cloud.client.remote_autostart - with patch( - "hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro() - ) as mock_disconnect: + with patch("hass_nabucasa.remote.RemoteUI.disconnect") as mock_disconnect: await hass.services.async_call(DOMAIN, "remote_disconnect", blocking=True) assert mock_disconnect.called @@ -82,9 +77,9 @@ async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): # Test admin access required non_admin_context = Context(user_id=hass_read_only_user.id) - with patch( - "hass_nabucasa.remote.RemoteUI.connect", return_value=mock_coro() - ) as mock_connect, pytest.raises(Unauthorized): + with patch("hass_nabucasa.remote.RemoteUI.connect") as mock_connect, pytest.raises( + Unauthorized + ): await hass.services.async_call( DOMAIN, "remote_connect", blocking=True, context=non_admin_context ) @@ -92,7 +87,7 @@ async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): assert mock_connect.called is False with patch( - "hass_nabucasa.remote.RemoteUI.disconnect", return_value=mock_coro() + "hass_nabucasa.remote.RemoteUI.disconnect" ) as mock_disconnect, pytest.raises(Unauthorized): await hass.services.async_call( DOMAIN, "remote_disconnect", blocking=True, context=non_admin_context @@ -103,7 +98,7 @@ async def test_remote_services(hass, mock_cloud_fixture, hass_read_only_user): async def test_startup_shutdown_events(hass, mock_cloud_fixture): """Test if the cloud will start on startup event.""" - with patch("hass_nabucasa.Cloud.stop", return_value=mock_coro()) as mock_stop: + with patch("hass_nabucasa.Cloud.stop") as mock_stop: hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() @@ -114,7 +109,7 @@ async def test_setup_existing_cloud_user(hass, hass_storage): """Test setup with API push default data.""" user = await hass.auth.async_create_system_user("Cloud test") hass_storage[STORAGE_KEY] = {"version": 1, "data": {"cloud_user": user.id}} - with patch("hass_nabucasa.Cloud.start", return_value=mock_coro()): + with patch("hass_nabucasa.Cloud.start"): result = await async_setup_component( hass, "cloud", @@ -148,9 +143,7 @@ async def test_on_connect(hass, mock_cloud_fixture): assert len(hass.states.async_entity_ids("binary_sensor")) == 1 - with patch( - "homeassistant.helpers.discovery.async_load_platform", side_effect=mock_coro - ) as mock_load: + with patch("homeassistant.helpers.discovery.async_load_platform") as mock_load: await cl.iot._on_connect[-1]() await hass.async_block_till_done() diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py index 358f952e191..cee86fc046e 100644 --- a/tests/components/config/test_config_entries.py +++ b/tests/components/config/test_config_entries.py @@ -1,7 +1,6 @@ """Test config entries API.""" from collections import OrderedDict -from unittest.mock import patch import pytest import voluptuous as vol @@ -13,10 +12,10 @@ from homeassistant.core import callback from homeassistant.generated import config_flows from homeassistant.setup import async_setup_component +from tests.async_mock import AsyncMock, patch from tests.common import ( MockConfigEntry, MockModule, - mock_coro_func, mock_entity_platform, mock_integration, ) @@ -228,7 +227,9 @@ async def test_create_account(hass, client): """Test a flow that creates an account.""" mock_entity_platform(hass, "config_flow.test", None) - mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) class TestFlow(core_ce.ConfigFlow): VERSION = 1 @@ -263,7 +264,9 @@ async def test_create_account(hass, client): async def test_two_step_flow(hass, client): """Test we can finish a two step flow.""" - mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): @@ -320,7 +323,9 @@ async def test_two_step_flow(hass, client): async def test_continue_flow_unauth(hass, client, hass_admin_user): """Test we can't finish a two step flow.""" - mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): @@ -516,7 +521,9 @@ async def test_options_flow(hass, client): async def test_two_step_options_flow(hass, client): """Test we can finish a two step options flow.""" - mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) class TestFlow(core_ce.ConfigFlow): @staticmethod @@ -666,7 +673,9 @@ async def test_update_entry_nonexisting(hass, hass_ws_client): async def test_ignore_flow(hass, hass_ws_client): """Test we can ignore a flow.""" assert await async_setup_component(hass, "config", {}) - mock_integration(hass, MockModule("test", async_setup_entry=mock_coro_func(True))) + mock_integration( + hass, MockModule("test", async_setup_entry=AsyncMock(return_value=True)) + ) mock_entity_platform(hass, "config_flow.test", None) class TestFlow(core_ce.ConfigFlow): diff --git a/tests/components/darksky/test_sensor.py b/tests/components/darksky/test_sensor.py index 2163a809b5e..2dc2a3ff30b 100644 --- a/tests/components/darksky/test_sensor.py +++ b/tests/components/darksky/test_sensor.py @@ -11,7 +11,7 @@ import requests_mock from homeassistant.components.darksky import sensor as darksky from homeassistant.setup import setup_component -from tests.common import MockDependency, get_test_home_assistant, load_fixture +from tests.common import get_test_home_assistant, load_fixture VALID_CONFIG_MINIMAL = { "sensor": { @@ -110,12 +110,11 @@ class TestDarkSkySetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @MockDependency("forecastio") @patch( "homeassistant.components.darksky.sensor.forecastio.load_forecast", new=load_forecastMock, ) - def test_setup_with_config(self, mock_forecastio): + def test_setup_with_config(self): """Test the platform setup with configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_MINIMAL) @@ -129,12 +128,11 @@ class TestDarkSkySetup(unittest.TestCase): state = self.hass.states.get("sensor.dark_sky_summary") assert state is None - @MockDependency("forecastio") @patch( "homeassistant.components.darksky.sensor.forecastio.load_forecast", new=load_forecastMock, ) - def test_setup_with_language_config(self, mock_forecastio): + def test_setup_with_language_config(self): """Test the platform setup with language configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_LANG_DE) @@ -164,12 +162,11 @@ class TestDarkSkySetup(unittest.TestCase): ) assert not response - @MockDependency("forecastio") @patch( "homeassistant.components.darksky.sensor.forecastio.load_forecast", new=load_forecastMock, ) - def test_setup_with_alerts_config(self, mock_forecastio): + def test_setup_with_alerts_config(self): """Test the platform setup with alert configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_ALERTS) diff --git a/tests/components/emulated_roku/test_binding.py b/tests/components/emulated_roku/test_binding.py index 53b6217fcbc..61c93690548 100644 --- a/tests/components/emulated_roku/test_binding.py +++ b/tests/components/emulated_roku/test_binding.py @@ -14,7 +14,7 @@ from homeassistant.components.emulated_roku.binding import ( EmulatedRoku, ) -from tests.common import mock_coro_func +from tests.async_mock import AsyncMock async def test_events_fired_properly(hass): @@ -39,7 +39,7 @@ async def test_events_fired_properly(hass): nonlocal roku_event_handler roku_event_handler = handler - return Mock(start=mock_coro_func(), close=mock_coro_func()) + return Mock(start=AsyncMock(), close=AsyncMock()) def listener(event): events.append(event) diff --git a/tests/components/emulated_roku/test_init.py b/tests/components/emulated_roku/test_init.py index efdf330a876..8d58519ddf9 100644 --- a/tests/components/emulated_roku/test_init.py +++ b/tests/components/emulated_roku/test_init.py @@ -4,14 +4,14 @@ from unittest.mock import Mock, patch from homeassistant.components import emulated_roku from homeassistant.setup import async_setup_component -from tests.common import mock_coro_func +from tests.async_mock import AsyncMock async def test_config_required_fields(hass): """Test that configuration is successful with required fields.""" with patch.object(emulated_roku, "configured_servers", return_value=[]), patch( "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=mock_coro_func(), close=mock_coro_func()), + return_value=Mock(start=AsyncMock(), close=AsyncMock()), ): assert ( await async_setup_component( @@ -36,7 +36,7 @@ async def test_config_already_registered_not_configured(hass): """Test that an already registered name causes the entry to be ignored.""" with patch( "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=mock_coro_func(), close=mock_coro_func()), + return_value=Mock(start=AsyncMock(), close=AsyncMock()), ) as instantiate, patch.object( emulated_roku, "configured_servers", return_value=["Emulated Roku Test"] ): @@ -75,7 +75,7 @@ async def test_setup_entry_successful(hass): with patch( "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=mock_coro_func(), close=mock_coro_func()), + return_value=Mock(start=AsyncMock(), close=AsyncMock()), ) as instantiate: assert await emulated_roku.async_setup_entry(hass, entry) is True @@ -99,7 +99,7 @@ async def test_unload_entry(hass): with patch( "homeassistant.components.emulated_roku.binding.EmulatedRokuServer", - return_value=Mock(start=mock_coro_func(), close=mock_coro_func()), + return_value=Mock(start=AsyncMock(), close=AsyncMock()), ): assert await emulated_roku.async_setup_entry(hass, entry) is True diff --git a/tests/components/folder_watcher/test_init.py b/tests/components/folder_watcher/test_init.py index 0702e64b4f8..fa2cfc6d3f1 100644 --- a/tests/components/folder_watcher/test_init.py +++ b/tests/components/folder_watcher/test_init.py @@ -5,8 +5,6 @@ from unittest.mock import Mock, patch from homeassistant.components import folder_watcher from homeassistant.setup import async_setup_component -from tests.common import MockDependency - async def test_invalid_path_setup(hass): """Test that an invalid path is not set up.""" @@ -29,8 +27,7 @@ async def test_valid_path_setup(hass): ) -@MockDependency("watchdog", "events") -def test_event(mock_watchdog): +def test_event(): """Check that Home Assistant events are fired correctly on watchdog event.""" class MockPatternMatchingEventHandler: @@ -39,17 +36,20 @@ def test_event(mock_watchdog): def __init__(self, patterns): pass - mock_watchdog.events.PatternMatchingEventHandler = MockPatternMatchingEventHandler - hass = Mock() - handler = folder_watcher.create_event_handler(["*"], hass) - handler.on_created( - Mock(is_directory=False, src_path="/hello/world.txt", event_type="created") - ) - assert hass.bus.fire.called - assert hass.bus.fire.mock_calls[0][1][0] == folder_watcher.DOMAIN - assert hass.bus.fire.mock_calls[0][1][1] == { - "event_type": "created", - "path": "/hello/world.txt", - "file": "world.txt", - "folder": "/hello", - } + with patch( + "homeassistant.components.folder_watcher.PatternMatchingEventHandler", + MockPatternMatchingEventHandler, + ): + hass = Mock() + handler = folder_watcher.create_event_handler(["*"], hass) + handler.on_created( + Mock(is_directory=False, src_path="/hello/world.txt", event_type="created") + ) + assert hass.bus.fire.called + assert hass.bus.fire.mock_calls[0][1][0] == folder_watcher.DOMAIN + assert hass.bus.fire.mock_calls[0][1][1] == { + "event_type": "created", + "path": "/hello/world.txt", + "file": "world.txt", + "folder": "/hello", + } diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index 414ad4e8cb0..b298fff6674 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -15,7 +15,7 @@ from homeassistant.components.http.view import ( ) from homeassistant.exceptions import ServiceNotFound, Unauthorized -from tests.common import mock_coro_func +from tests.async_mock import AsyncMock @pytest.fixture @@ -38,7 +38,7 @@ async def test_handling_unauthorized(mock_request): """Test handling unauth exceptions.""" with pytest.raises(HTTPUnauthorized): await request_handler_factory( - Mock(requires_auth=False), mock_coro_func(exception=Unauthorized) + Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized) )(mock_request) @@ -46,7 +46,7 @@ async def test_handling_invalid_data(mock_request): """Test handling unauth exceptions.""" with pytest.raises(HTTPBadRequest): await request_handler_factory( - Mock(requires_auth=False), mock_coro_func(exception=vol.Invalid("yo")) + Mock(requires_auth=False), AsyncMock(side_effect=vol.Invalid("yo")) )(mock_request) @@ -55,5 +55,5 @@ async def test_handling_service_not_found(mock_request): with pytest.raises(HTTPInternalServerError): await request_handler_factory( Mock(requires_auth=False), - mock_coro_func(exception=ServiceNotFound("test", "test")), + AsyncMock(side_effect=ServiceNotFound("test", "test")), )(mock_request) diff --git a/tests/components/iqvia/test_config_flow.py b/tests/components/iqvia/test_config_flow.py index 9345ff5b2ad..4cc30958b23 100644 --- a/tests/components/iqvia/test_config_flow.py +++ b/tests/components/iqvia/test_config_flow.py @@ -1,17 +1,8 @@ """Define tests for the IQVIA config flow.""" -import pytest - from homeassistant import data_entry_flow from homeassistant.components.iqvia import CONF_ZIP_CODE, DOMAIN, config_flow -from tests.common import MockConfigEntry, MockDependency - - -@pytest.fixture -def mock_pyiqvia(): - """Mock the pyiqvia library.""" - with MockDependency("pyiqvia") as mock_pyiqvia_: - yield mock_pyiqvia_ +from tests.common import MockConfigEntry async def test_duplicate_error(hass): @@ -26,7 +17,7 @@ async def test_duplicate_error(hass): assert result["errors"] == {CONF_ZIP_CODE: "identifier_exists"} -async def test_invalid_zip_code(hass, mock_pyiqvia): +async def test_invalid_zip_code(hass): """Test that an invalid ZIP code key throws an error.""" conf = {CONF_ZIP_CODE: "abcde"} @@ -48,7 +39,7 @@ async def test_show_form(hass): assert result["step_id"] == "user" -async def test_step_import(hass, mock_pyiqvia): +async def test_step_import(hass): """Test that the import step works.""" conf = {CONF_ZIP_CODE: "12345"} @@ -61,7 +52,7 @@ async def test_step_import(hass, mock_pyiqvia): assert result["data"] == {CONF_ZIP_CODE: "12345"} -async def test_step_user(hass, mock_pyiqvia): +async def test_step_user(hass): """Test that the user step works.""" conf = {CONF_ZIP_CODE: "12345"} diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index 8976f85f3d1..00c565aca8c 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -16,7 +16,8 @@ from homeassistant.components.melissa import DATA_MELISSA, climate as melissa from homeassistant.components.melissa.climate import MelissaClimate from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from tests.common import load_fixture, mock_coro_func +from tests.async_mock import AsyncMock +from tests.common import load_fixture _SERIAL = "12345678" @@ -24,17 +25,17 @@ _SERIAL = "12345678" def melissa_mock(): """Use this to mock the melissa api.""" api = Mock() - api.async_fetch_devices = mock_coro_func( + api.async_fetch_devices = AsyncMock( return_value=json.loads(load_fixture("melissa_fetch_devices.json")) ) - api.async_status = mock_coro_func( + api.async_status = AsyncMock( return_value=json.loads(load_fixture("melissa_status.json")) ) - api.async_cur_settings = mock_coro_func( + api.async_cur_settings = AsyncMock( return_value=json.loads(load_fixture("melissa_cur_settings.json")) ) - api.async_send = mock_coro_func(return_value=True) + api.async_send = AsyncMock(return_value=True) api.STATE_OFF = 0 api.STATE_ON = 1 @@ -276,7 +277,7 @@ async def test_send(hass): await thermostat.async_send({"fan": api.FAN_MEDIUM}) await hass.async_block_till_done() assert SPEED_MEDIUM == thermostat.fan_mode - api.async_send.return_value = mock_coro_func(return_value=False) + api.async_send.return_value = AsyncMock(return_value=False) thermostat._cur_settings = None await thermostat.async_send({"fan": api.FAN_LOW}) await hass.async_block_till_done() @@ -296,7 +297,7 @@ async def test_update(hass): await thermostat.async_update() assert SPEED_LOW == thermostat.fan_mode assert HVAC_MODE_HEAT == thermostat.state - api.async_status = mock_coro_func(exception=KeyError("boom")) + 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 diff --git a/tests/components/melissa/test_init.py b/tests/components/melissa/test_init.py index 892f4d60a44..7e174a4f8a0 100644 --- a/tests/components/melissa/test_init.py +++ b/tests/components/melissa/test_init.py @@ -1,23 +1,22 @@ """The test for the Melissa Climate component.""" from homeassistant.components import melissa -from tests.common import MockDependency, mock_coro_func +from tests.async_mock import AsyncMock, patch VALID_CONFIG = {"melissa": {"username": "********", "password": "********"}} async def test_setup(hass): """Test setting up the Melissa component.""" - with MockDependency("melissa") as mocked_melissa: - melissa.melissa = mocked_melissa - mocked_melissa.AsyncMelissa().async_connect = mock_coro_func() + with patch("melissa.AsyncMelissa") as mocked_melissa, patch.object( + melissa, "async_load_platform" + ): + mocked_melissa.return_value.async_connect = AsyncMock() await melissa.async_setup(hass, VALID_CONFIG) - mocked_melissa.AsyncMelissa.assert_called_with( - username="********", password="********" - ) + mocked_melissa.assert_called_with(username="********", password="********") assert melissa.DATA_MELISSA in hass.data assert isinstance( - hass.data[melissa.DATA_MELISSA], type(mocked_melissa.AsyncMelissa()) + hass.data[melissa.DATA_MELISSA], type(mocked_melissa.return_value), ) diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index aa72549152e..0990accec9f 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -1,18 +1,18 @@ """Test config flow.""" -from unittest.mock import patch import pytest from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, mock_coro +from tests.async_mock import patch +from tests.common import MockConfigEntry @pytest.fixture(autouse=True) def mock_finish_setup(): """Mock out the finish setup method.""" with patch( - "homeassistant.components.mqtt.MQTT.async_connect", return_value=mock_coro(True) + "homeassistant.components.mqtt.MQTT.async_connect", return_value=True ) as mock_finish: yield mock_finish diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index c44c62fe080..51de8229347 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -3,7 +3,6 @@ import copy import unittest from unittest.mock import patch -from homeassistant.components.reddit import sensor as reddit_sensor from homeassistant.components.reddit.sensor import ( ATTR_BODY, ATTR_COMMENTS_NUMBER, @@ -20,7 +19,7 @@ from homeassistant.components.reddit.sensor import ( from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import setup_component -from tests.common import MockDependency, get_test_home_assistant +from tests.common import get_test_home_assistant VALID_CONFIG = { "sensor": { @@ -157,12 +156,10 @@ class TestRedditSetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @MockDependency("praw") @patch("praw.Reddit", new=MockPraw) - def test_setup_with_valid_config(self, mock_praw): + def test_setup_with_valid_config(self): """Test the platform setup with Reddit configuration.""" - with patch.object(reddit_sensor, "praw", mock_praw): - setup_component(self.hass, "sensor", VALID_CONFIG) + setup_component(self.hass, "sensor", VALID_CONFIG) state = self.hass.states.get("sensor.reddit_worldnews") assert int(state.state) == MOCK_RESULTS_LENGTH @@ -184,9 +181,8 @@ class TestRedditSetup(unittest.TestCase): assert state.attributes[CONF_SORT_BY] == "hot" - @MockDependency("praw") @patch("praw.Reddit", new=MockPraw) - def test_setup_with_invalid_config(self, mock_praw): + def test_setup_with_invalid_config(self): """Test the platform setup with invalid Reddit configuration.""" setup_component(self.hass, "sensor", INVALID_SORT_BY_CONFIG) assert not self.hass.states.get("sensor.reddit_worldnews") diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 10ec22f8b67..62f93d7cd70 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component from homeassistant.util import utcnow -from tests.common import MockDependency, async_fire_time_changed +from tests.common import async_fire_time_changed VALID_CONFIG_MINIMAL = { "sensor": { @@ -110,15 +110,8 @@ class ProfileMock: return self.__class__.summary_data -@pytest.fixture(autouse=True, name="mock_py17track") -def fixture_mock_py17track(): - """Mock py17track dependency.""" - with MockDependency("py17track"): - yield - - @pytest.fixture(autouse=True, name="mock_client") -def fixture_mock_client(mock_py17track): +def fixture_mock_client(): """Mock py17track client.""" with mock.patch( "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", diff --git a/tests/components/tplink/test_init.py b/tests/components/tplink/test_init.py index d8e3f76cac3..290151b10cc 100644 --- a/tests/components/tplink/test_init.py +++ b/tests/components/tplink/test_init.py @@ -16,14 +16,12 @@ from homeassistant.components.tplink.common import ( from homeassistant.const import CONF_HOST from homeassistant.setup import async_setup_component -from tests.common import MockConfigEntry, MockDependency, mock_coro - -MOCK_PYHS100 = MockDependency("pyHS100") +from tests.common import MockConfigEntry, mock_coro async def test_creating_entry_tries_discover(hass): """Test setting up does discovery.""" - with MOCK_PYHS100, patch( + with patch( "homeassistant.components.tplink.async_setup_entry", return_value=mock_coro(True), ) as mock_setup, patch( @@ -47,9 +45,7 @@ async def test_creating_entry_tries_discover(hass): async def test_configuring_tplink_causes_discovery(hass): """Test that specifying empty config does discovery.""" - with MOCK_PYHS100, patch( - "homeassistant.components.tplink.common.Discover.discover" - ) as discover: + with patch("homeassistant.components.tplink.common.Discover.discover") as discover: discover.return_value = {"host": 1234} await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}}) await hass.async_block_till_done() @@ -177,7 +173,7 @@ async def test_is_dimmable(hass): async def test_configuring_discovery_disabled(hass): """Test that discover does not get called when disabled.""" - with MOCK_PYHS100, patch( + with patch( "homeassistant.components.tplink.async_setup_entry", return_value=mock_coro(True), ) as mock_setup, patch( @@ -226,7 +222,7 @@ async def test_platforms_are_initialized(hass): async def test_no_config_creates_no_entry(hass): """Test for when there is no tplink in config.""" - with MOCK_PYHS100, patch( + with patch( "homeassistant.components.tplink.async_setup_entry", return_value=mock_coro(True), ) as mock_setup: diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py index 4c4d499a6d9..ee7f072a65c 100644 --- a/tests/components/twilio/test_init.py +++ b/tests/components/twilio/test_init.py @@ -5,34 +5,31 @@ from homeassistant import data_entry_flow from homeassistant.components import twilio from homeassistant.core import callback -from tests.common import MockDependency - async def test_config_flow_registers_webhook(hass, aiohttp_client): """Test setting up Twilio and sending webhook.""" - with MockDependency("twilio", "rest"), MockDependency("twilio", "twiml"): - with patch("homeassistant.util.get_local_ip", return_value="example.com"): - result = await hass.config_entries.flow.async_init( - "twilio", context={"source": "user"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM, result + with patch("homeassistant.util.get_local_ip", return_value="example.com"): + result = await hass.config_entries.flow.async_init( + "twilio", context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM, result - result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - webhook_id = result["result"].data["webhook_id"] + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + webhook_id = result["result"].data["webhook_id"] - twilio_events = [] + twilio_events = [] - @callback - def handle_event(event): - """Handle Twilio event.""" - twilio_events.append(event) + @callback + def handle_event(event): + """Handle Twilio event.""" + twilio_events.append(event) - hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event) + hass.bus.async_listen(twilio.RECEIVED_DATA, handle_event) - client = await aiohttp_client(hass.http.app) - await client.post(f"/api/webhook/{webhook_id}", data={"hello": "twilio"}) + client = await aiohttp_client(hass.http.app) + await client.post(f"/api/webhook/{webhook_id}", data={"hello": "twilio"}) - assert len(twilio_events) == 1 - assert twilio_events[0].data["webhook_id"] == webhook_id - assert twilio_events[0].data["hello"] == "twilio" + assert len(twilio_events) == 1 + assert twilio_events[0].data["webhook_id"] == webhook_id + assert twilio_events[0].data["hello"] == "twilio" diff --git a/tests/components/updater/test_init.py b/tests/components/updater/test_init.py index 95875828c71..0d710907f59 100644 --- a/tests/components/updater/test_init.py +++ b/tests/components/updater/test_init.py @@ -6,7 +6,7 @@ from homeassistant.helpers.update_coordinator import UpdateFailed from homeassistant.setup import async_setup_component from tests.async_mock import patch -from tests.common import MockDependency, mock_component +from tests.common import mock_component NEW_VERSION = "10000.0" MOCK_VERSION = "10.0" @@ -17,13 +17,6 @@ MOCK_CONFIG = {updater.DOMAIN: {"reporting": True}} RELEASE_NOTES = "test release notes" -@pytest.fixture(autouse=True) -def mock_distro(): - """Mock distro dep.""" - with MockDependency("distro"): - yield - - @pytest.fixture(autouse=True) def mock_version(): """Mock current version.""" diff --git a/tests/components/wake_on_lan/test_init.py b/tests/components/wake_on_lan/test_init.py index c2ee0930895..6eb7afb29f4 100644 --- a/tests/components/wake_on_lan/test_init.py +++ b/tests/components/wake_on_lan/test_init.py @@ -2,21 +2,18 @@ import pytest import voluptuous as vol -from homeassistant.components import wake_on_lan from homeassistant.components.wake_on_lan import DOMAIN, SERVICE_SEND_MAGIC_PACKET from homeassistant.setup import async_setup_component -from tests.common import MockDependency +from tests.async_mock import patch async def test_send_magic_packet(hass): """Test of send magic packet service call.""" - with MockDependency("wakeonlan") as mocked_wakeonlan: + with patch("homeassistant.components.wake_on_lan.wakeonlan") as mocked_wakeonlan: mac = "aa:bb:cc:dd:ee:ff" bc_ip = "192.168.255.255" - wake_on_lan.wakeonlan = mocked_wakeonlan - await async_setup_component(hass, DOMAIN, {}) await hass.services.async_call( diff --git a/tests/components/yandex_transport/test_yandex_transport_sensor.py b/tests/components/yandex_transport/test_yandex_transport_sensor.py index f0664e4f045..f60e7ba5adf 100644 --- a/tests/components/yandex_transport/test_yandex_transport_sensor.py +++ b/tests/components/yandex_transport/test_yandex_transport_sensor.py @@ -8,12 +8,8 @@ import homeassistant.components.sensor as sensor from homeassistant.const import CONF_NAME import homeassistant.util.dt as dt_util -from tests.common import ( - MockDependency, - assert_setup_component, - async_setup_component, - load_fixture, -) +from tests.async_mock import patch +from tests.common import assert_setup_component, async_setup_component, load_fixture REPLY = json.loads(load_fixture("yandex_transport_reply.json")) @@ -21,8 +17,8 @@ REPLY = json.loads(load_fixture("yandex_transport_reply.json")) @pytest.fixture def mock_requester(): """Create a mock ya_ma module and YandexMapsRequester.""" - with MockDependency("ya_ma") as ya_ma: - instance = ya_ma.YandexMapsRequester.return_value + with patch("ya_ma.YandexMapsRequester") as requester: + instance = requester.return_value instance.get_stop_info.return_value = REPLY yield instance From 6056753a9c116f8ef5f24243cfb310ebaff916d8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 30 Apr 2020 16:47:14 -0700 Subject: [PATCH 167/511] Introduce a singleton decorator (#34803) --- homeassistant/core.py | 2 +- homeassistant/helpers/device_registry.py | 28 ++++----------- homeassistant/helpers/entity_registry.py | 28 ++++----------- homeassistant/helpers/singleton.py | 44 ++++++++++++++++++++++++ pylintrc | 2 +- 5 files changed, 60 insertions(+), 44 deletions(-) create mode 100644 homeassistant/helpers/singleton.py diff --git a/homeassistant/core.py b/homeassistant/core.py index 045e56ecb53..4d11d970c53 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -83,8 +83,8 @@ if TYPE_CHECKING: block_async_io.enable() fix_threading_exception_logging() -# pylint: disable=invalid-name T = TypeVar("T") +# pylint: disable=invalid-name CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) CALLBACK_TYPE = Callable[[], None] # pylint: enable=invalid-name diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index ef85ac953f6..56b8170b99a 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,8 +1,7 @@ """Provide a way to connect entities belonging to one device.""" -from asyncio import Event from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional import uuid import attr @@ -10,6 +9,7 @@ import attr from homeassistant.core import callback from homeassistant.loader import bind_hass +from .singleton import singleton from .typing import HomeAssistantType # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs @@ -356,26 +356,12 @@ class DeviceRegistry: @bind_hass +@singleton(DATA_REGISTRY) async def async_get_registry(hass: HomeAssistantType) -> DeviceRegistry: - """Return device registry instance.""" - reg_or_evt = hass.data.get(DATA_REGISTRY) - - if not reg_or_evt: - evt = hass.data[DATA_REGISTRY] = Event() - - reg = DeviceRegistry(hass) - await reg.async_load() - - hass.data[DATA_REGISTRY] = reg - evt.set() - return reg - - if isinstance(reg_or_evt, Event): - evt = reg_or_evt - await evt.wait() - return cast(DeviceRegistry, hass.data.get(DATA_REGISTRY)) - - return cast(DeviceRegistry, reg_or_evt) + """Create entity registry.""" + reg = DeviceRegistry(hass) + await reg.async_load() + return reg @callback diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index 10de8564fca..f276cc49850 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -7,7 +7,6 @@ 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. """ -import asyncio from collections import OrderedDict import logging from typing import ( @@ -39,6 +38,7 @@ from homeassistant.loader import bind_hass from homeassistant.util import slugify from homeassistant.util.yaml import load_yaml +from .singleton import singleton from .typing import HomeAssistantType if TYPE_CHECKING: @@ -492,26 +492,12 @@ class EntityRegistry: @bind_hass +@singleton(DATA_REGISTRY) async def async_get_registry(hass: HomeAssistantType) -> EntityRegistry: - """Return entity registry instance.""" - reg_or_evt = hass.data.get(DATA_REGISTRY) - - if not reg_or_evt: - evt = hass.data[DATA_REGISTRY] = asyncio.Event() - - reg = EntityRegistry(hass) - await reg.async_load() - - hass.data[DATA_REGISTRY] = reg - evt.set() - return reg - - if isinstance(reg_or_evt, asyncio.Event): - evt = reg_or_evt - await evt.wait() - return cast(EntityRegistry, hass.data.get(DATA_REGISTRY)) - - return cast(EntityRegistry, reg_or_evt) + """Create entity registry.""" + reg = EntityRegistry(hass) + await reg.async_load() + return reg @callback @@ -621,4 +607,4 @@ async def async_migrate_entries( updates = entry_callback(entry) if updates is not None: - ent_reg.async_update_entity(entry.entity_id, **updates) # type: ignore + ent_reg.async_update_entity(entry.entity_id, **updates) diff --git a/homeassistant/helpers/singleton.py b/homeassistant/helpers/singleton.py new file mode 100644 index 00000000000..8d0f6873c69 --- /dev/null +++ b/homeassistant/helpers/singleton.py @@ -0,0 +1,44 @@ +"""Helper to help coordinating calls.""" +import asyncio +import functools +from typing import Awaitable, Callable, TypeVar, cast + +from homeassistant.core import HomeAssistant + +T = TypeVar("T") + +FUNC = Callable[[HomeAssistant], Awaitable[T]] + + +def singleton(data_key: str) -> Callable[[FUNC], FUNC]: + """Decorate a function that should be called once per instance. + + Result will be cached and simultaneous calls will be handled. + """ + + def wrapper(func: FUNC) -> FUNC: + """Wrap a function with caching logic.""" + + @functools.wraps(func) + async def wrapped(hass: HomeAssistant) -> T: + obj_or_evt = hass.data.get(data_key) + + if not obj_or_evt: + evt = hass.data[data_key] = asyncio.Event() + + result = await func(hass) + + hass.data[data_key] = result + evt.set() + return cast(T, result) + + if isinstance(obj_or_evt, asyncio.Event): + evt = obj_or_evt + await evt.wait() + return cast(T, hass.data.get(data_key)) + + return cast(T, obj_or_evt) + + return wrapped + + return wrapper diff --git a/pylintrc b/pylintrc index a8118d23910..dd763d05886 100644 --- a/pylintrc +++ b/pylintrc @@ -8,7 +8,7 @@ persistent=no extension-pkg-whitelist=ciso8601 [BASIC] -good-names=id,i,j,k,ex,Run,_,fp +good-names=id,i,j,k,ex,Run,_,fp,T [MESSAGES CONTROL] # Reasons disabled: From bd72ddda3c7240c00a9002500401ac66d96be182 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 1 May 2020 00:02:55 +0000 Subject: [PATCH 168/511] [ci skip] Translation update --- .../components/adguard/translations/ru.json | 2 +- .../components/airvisual/translations/de.json | 3 +- .../components/airvisual/translations/fi.json | 24 ++++++++++++++ .../components/airvisual/translations/hu.json | 15 +++++++++ .../components/airvisual/translations/nl.json | 31 ++++++++++++++++-- .../components/airvisual/translations/sv.json | 31 ++++++++++++++++++ .../alarm_control_panel/translations/it.json | 6 ++-- .../alarm_control_panel/translations/nl.json | 9 +++++- .../components/atag/translations/fi.json | 14 ++++++++ .../components/atag/translations/hu.json | 11 +++++++ .../components/atag/translations/sv.json | 16 ++++++++++ .../components/august/translations/nl.json | 31 ++++++++++++++++++ .../components/august/translations/sv.json | 24 ++++++++++++++ .../binary_sensor/translations/it.json | 2 +- .../components/braviatv/translations/nl.json | 13 +++++++- .../components/braviatv/translations/ru.json | 2 +- .../components/braviatv/translations/sv.json | 25 +++++++++++++++ .../cert_expiry/translations/nl.json | 5 +++ .../cert_expiry/translations/sv.json | 1 + .../configurator/translations/it.json | 2 +- .../coronavirus/translations/sv.json | 15 +++++++++ .../components/cover/translations/nl.json | 8 +++++ .../components/deconz/translations/sv.json | 6 ++++ .../components/directv/translations/nl.json | 24 ++++++++++++++ .../components/directv/translations/sv.json | 20 ++++++++++++ .../components/doorbird/translations/nl.json | 24 +++++++++++++- .../components/doorbird/translations/sv.json | 19 +++++++++++ .../components/elkm1/translations/nl.json | 11 ++++++- .../components/elkm1/translations/sv.json | 16 ++++++++++ .../components/esphome/translations/en.json | 2 +- .../components/flume/translations/nl.json | 2 ++ .../components/flume/translations/sv.json | 21 ++++++++++++ .../flunearyou/translations/nl.json | 4 ++- .../flunearyou/translations/sv.json | 18 +++++++++++ .../components/freebox/translations/nl.json | 25 +++++++++++++++ .../components/freebox/translations/sv.json | 20 ++++++++++++ .../components/fritzbox/translations/de.json | 3 +- .../components/fritzbox/translations/fi.json | 19 +++++++++++ .../components/fritzbox/translations/nl.json | 3 +- .../components/fritzbox/translations/sv.json | 23 +++++++++++++ .../geonetnz_quakes/translations/sv.json | 3 ++ .../components/griddy/translations/nl.json | 17 ++++++++++ .../components/griddy/translations/sv.json | 8 +++++ .../components/harmony/translations/nl.json | 14 +++++++- .../components/harmony/translations/sv.json | 21 ++++++++++++ .../components/hue/translations/nl.json | 1 + .../components/hue/translations/sv.json | 17 ++++++++++ .../translations/de.json | 24 ++++++++++++++ .../translations/es.json | 24 ++++++++++++++ .../translations/fi.json | 20 ++++++++++++ .../translations/hu.json | 18 +++++++++++ .../translations/it.json | 24 ++++++++++++++ .../translations/nl.json | 24 ++++++++++++++ .../translations/ru.json | 24 ++++++++++++++ .../translations/sv.json | 23 +++++++++++++ .../translations/zh-Hant.json | 24 ++++++++++++++ .../components/icloud/translations/nl.json | 6 ++-- .../components/ipp/translations/nl.json | 22 +++++++++++++ .../components/ipp/translations/ru.json | 2 +- .../components/konnected/translations/nl.json | 17 ++++++++-- .../components/light/translations/it.json | 4 +-- .../components/light/translations/nl.json | 2 ++ .../components/local_ip/translations/nl.json | 3 ++ .../components/local_ip/translations/sv.json | 3 ++ .../components/monoprice/translations/nl.json | 8 ++++- .../components/monoprice/translations/sv.json | 16 ++++++++++ .../components/mqtt/translations/nl.json | 11 +++++++ .../components/mqtt/translations/sv.json | 12 +++++++ .../components/myq/translations/sv.json | 16 ++++++++++ .../components/nexia/translations/nl.json | 8 +++++ .../components/nexia/translations/sv.json | 17 ++++++++++ .../components/notion/translations/nl.json | 3 ++ .../components/notion/translations/sv.json | 3 ++ .../components/nuheat/translations/nl.json | 24 ++++++++++++++ .../components/nuheat/translations/sv.json | 17 ++++++++++ .../components/nut/translations/nl.json | 14 ++++++-- .../components/nut/translations/sv.json | 26 +++++++++++++++ .../components/nws/translations/fi.json | 16 ++++++++++ .../components/nws/translations/nl.json | 14 ++++++-- .../components/nws/translations/sv.json | 19 +++++++++++ .../panasonic_viera/translations/fi.json | 28 ++++++++++++++++ .../panasonic_viera/translations/sv.json | 15 +++++++++ .../components/powerwall/translations/nl.json | 20 ++++++++++++ .../components/powerwall/translations/sv.json | 16 ++++++++++ .../pvpc_hourly_pricing/translations/nl.json | 10 ++++-- .../components/rachio/translations/nl.json | 30 +++++++++++++++++ .../components/rachio/translations/sv.json | 12 +++++++ .../rainmachine/translations/nl.json | 3 ++ .../components/roku/translations/nl.json | 25 +++++++++++++++ .../components/roku/translations/sv.json | 7 ++++ .../components/roomba/translations/nl.json | 32 +++++++++++++++++++ .../components/roomba/translations/sv.json | 29 +++++++++++++++++ .../season/translations/sensor.fi.json | 10 +++--- .../season/translations/sensor.sv.json | 6 ++++ .../components/sense/translations/nl.json | 21 ++++++++++++ .../components/sense/translations/sv.json | 20 ++++++++++++ .../shopping_list/translations/nl.json | 14 ++++++++ .../simplisafe/translations/de.json | 1 + .../simplisafe/translations/nl.json | 13 ++++++++ .../smartthings/translations/nl.json | 6 ++++ .../smartthings/translations/sv.json | 14 ++++++++ .../synology_dsm/translations/fi.json | 14 ++++++++ .../synology_dsm/translations/nl.json | 1 + .../synology_dsm/translations/sv.json | 27 ++++++++++++++++ .../components/tado/translations/nl.json | 24 +++++++++++++- .../components/tado/translations/sv.json | 20 ++++++++++++ .../components/tesla/translations/nl.json | 1 + .../components/timer/translations/it.json | 6 ++-- .../totalconnect/translations/nl.json | 3 +- .../totalconnect/translations/sv.json | 18 +++++++++++ .../components/unifi/translations/nl.json | 16 +++++++--- .../components/unifi/translations/sv.json | 3 +- .../components/vera/translations/nl.json | 13 ++++++-- .../components/vizio/translations/nl.json | 16 ++++++++++ .../components/weather/translations/it.json | 2 +- .../components/wled/translations/ru.json | 2 +- .../xiaomi_miio/translations/de.json | 29 +++++++++++++++++ .../xiaomi_miio/translations/fi.json | 28 ++++++++++++++++ .../xiaomi_miio/translations/hu.json | 22 +++++++++++++ .../xiaomi_miio/translations/nl.json | 12 +++++-- .../xiaomi_miio/translations/sv.json | 28 ++++++++++++++++ .../components/zwave/translations/it.json | 4 +-- 122 files changed, 1683 insertions(+), 57 deletions(-) create mode 100644 homeassistant/components/airvisual/translations/fi.json create mode 100644 homeassistant/components/airvisual/translations/hu.json create mode 100644 homeassistant/components/airvisual/translations/sv.json create mode 100644 homeassistant/components/atag/translations/fi.json create mode 100644 homeassistant/components/atag/translations/hu.json create mode 100644 homeassistant/components/atag/translations/sv.json create mode 100644 homeassistant/components/august/translations/nl.json create mode 100644 homeassistant/components/august/translations/sv.json create mode 100644 homeassistant/components/braviatv/translations/sv.json create mode 100644 homeassistant/components/coronavirus/translations/sv.json create mode 100644 homeassistant/components/directv/translations/nl.json create mode 100644 homeassistant/components/directv/translations/sv.json create mode 100644 homeassistant/components/doorbird/translations/sv.json create mode 100644 homeassistant/components/elkm1/translations/sv.json create mode 100644 homeassistant/components/flume/translations/sv.json create mode 100644 homeassistant/components/flunearyou/translations/sv.json create mode 100644 homeassistant/components/freebox/translations/nl.json create mode 100644 homeassistant/components/freebox/translations/sv.json create mode 100644 homeassistant/components/fritzbox/translations/fi.json create mode 100644 homeassistant/components/fritzbox/translations/sv.json create mode 100644 homeassistant/components/griddy/translations/nl.json create mode 100644 homeassistant/components/griddy/translations/sv.json create mode 100644 homeassistant/components/harmony/translations/sv.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/de.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/es.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/fi.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/hu.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/it.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/nl.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/ru.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/sv.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/zh-Hant.json create mode 100644 homeassistant/components/monoprice/translations/sv.json create mode 100644 homeassistant/components/myq/translations/sv.json create mode 100644 homeassistant/components/nexia/translations/sv.json create mode 100644 homeassistant/components/nuheat/translations/nl.json create mode 100644 homeassistant/components/nuheat/translations/sv.json create mode 100644 homeassistant/components/nut/translations/sv.json create mode 100644 homeassistant/components/nws/translations/fi.json create mode 100644 homeassistant/components/nws/translations/sv.json create mode 100644 homeassistant/components/panasonic_viera/translations/fi.json create mode 100644 homeassistant/components/panasonic_viera/translations/sv.json create mode 100644 homeassistant/components/powerwall/translations/nl.json create mode 100644 homeassistant/components/powerwall/translations/sv.json create mode 100644 homeassistant/components/rachio/translations/nl.json create mode 100644 homeassistant/components/rachio/translations/sv.json create mode 100644 homeassistant/components/roku/translations/nl.json create mode 100644 homeassistant/components/roku/translations/sv.json create mode 100644 homeassistant/components/roomba/translations/nl.json create mode 100644 homeassistant/components/roomba/translations/sv.json create mode 100644 homeassistant/components/sense/translations/nl.json create mode 100644 homeassistant/components/sense/translations/sv.json create mode 100644 homeassistant/components/shopping_list/translations/nl.json create mode 100644 homeassistant/components/synology_dsm/translations/fi.json create mode 100644 homeassistant/components/synology_dsm/translations/sv.json create mode 100644 homeassistant/components/tado/translations/sv.json create mode 100644 homeassistant/components/totalconnect/translations/sv.json create mode 100644 homeassistant/components/xiaomi_miio/translations/de.json create mode 100644 homeassistant/components/xiaomi_miio/translations/fi.json create mode 100644 homeassistant/components/xiaomi_miio/translations/hu.json create mode 100644 homeassistant/components/xiaomi_miio/translations/sv.json diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 8c83b8c024c..1287b408544 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -23,7 +23,7 @@ "username": "\u041b\u043e\u0433\u0438\u043d", "verify_ssl": "AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u044d\u0442\u043e\u0442 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \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.", + "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.", "title": "AdGuard Home" } } diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index a8b2d296560..5e66daa3919 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -14,7 +14,8 @@ "api_key": "API-Schl\u00fcssel", "latitude": "Breitengrad", "longitude": "L\u00e4ngengrad" - } + }, + "title": "Konfigurieren Sie eine Geografie" }, "node_pro": { "data": { diff --git a/homeassistant/components/airvisual/translations/fi.json b/homeassistant/components/airvisual/translations/fi.json new file mode 100644 index 00000000000..51426854fdb --- /dev/null +++ b/homeassistant/components/airvisual/translations/fi.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "geography": { + "data": { + "api_key": "API-avain", + "latitude": "Leveysaste", + "longitude": "Pituusaste" + } + }, + "node_pro": { + "data": { + "password": "Salasana" + } + }, + "user": { + "data": { + "cloud_api": "Maantieteellinen sijainti", + "type": "Integrointityyppi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/hu.json b/homeassistant/components/airvisual/translations/hu.json new file mode 100644 index 00000000000..f1c4dadef11 --- /dev/null +++ b/homeassistant/components/airvisual/translations/hu.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "general_error": "Ismeretlen hiba t\u00f6rt\u00e9nt." + }, + "step": { + "geography": { + "data": { + "latitude": "Sz\u00e9less\u00e9g", + "longitude": "Hossz\u00fas\u00e1g" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index 62a9919e5f3..97b083b91fa 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "already_configured": "Deze co\u00f6rdinaten of Node / Pro ID zijn al geregistreerd." + }, "error": { - "general_error": "Er is een onbekende fout opgetreden." + "general_error": "Er is een onbekende fout opgetreden.", + "invalid_api_key": "Ongeldige API-sleutel opgegeven.", + "unable_to_connect": "Kan geen verbinding maken met Node / Pro-apparaat." }, "step": { "geography": { @@ -10,19 +15,39 @@ "latitude": "Breedtegraad", "longitude": "Lengtegraad" }, + "description": "Gebruik de AirVisual cloud API om een geografische locatie te bewaken.", "title": "Configureer een geografie" }, "node_pro": { "data": { "ip_address": "IP adres/hostname van unit", "password": "Wachtwoord van unit" - } + }, + "description": "Monitor een persoonlijke AirVisual-eenheid. Het wachtwoord kan worden opgehaald uit de gebruikersinterface van het apparaat.", + "title": "Configureer een AirVisual Node / Pro" }, "user": { "data": { + "api_key": "API-sleutel", "cloud_api": "Geografische ligging", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "node_pro": "AirVisual Node Pro", "type": "Integratietype" - } + }, + "description": "Kies welk type AirVisual-gegevens u wilt bewaken.", + "title": "Configureer AirVisual" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_on_map": "Toon gecontroleerde geografie op de kaart" + }, + "description": "Stel verschillende opties in voor de AirVisual-integratie.", + "title": "Configureer AirVisual" } } } diff --git a/homeassistant/components/airvisual/translations/sv.json b/homeassistant/components/airvisual/translations/sv.json new file mode 100644 index 00000000000..12911fa76d7 --- /dev/null +++ b/homeassistant/components/airvisual/translations/sv.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade." + }, + "step": { + "geography": { + "data": { + "api_key": "API-nyckel", + "latitude": "Latitud", + "longitude": "Longitud" + } + }, + "node_pro": { + "data": { + "ip_address": "Enhets IP-adress / v\u00e4rdnamn", + "password": "Enhetsl\u00f6senord" + } + }, + "user": { + "data": { + "api_key": "API-nyckel", + "cloud_api": "Geografisk Plats", + "latitude": "Latitud", + "longitude": "Longitud", + "type": "Integrationstyp" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/it.json b/homeassistant/components/alarm_control_panel/translations/it.json index a365c5cd35b..1574f88541b 100644 --- a/homeassistant/components/alarm_control_panel/translations/it.json +++ b/homeassistant/components/alarm_control_panel/translations/it.json @@ -26,12 +26,12 @@ "_": { "armed": "Attivo", "armed_away": "Attivo fuori casa", - "armed_custom_bypass": "Attivo con bypass", + "armed_custom_bypass": "Attivo con bypass personalizzato", "armed_home": "Attivo in casa", "armed_night": "Attivo Notte", - "arming": "In attivazione", + "arming": "In Attivazione", "disarmed": "Disattivo", - "disarming": "In disattivazione", + "disarming": "In Disattivazione", "pending": "In sospeso", "triggered": "Attivato" } diff --git a/homeassistant/components/alarm_control_panel/translations/nl.json b/homeassistant/components/alarm_control_panel/translations/nl.json index a1a00e7c9e3..15b5fd8457c 100644 --- a/homeassistant/components/alarm_control_panel/translations/nl.json +++ b/homeassistant/components/alarm_control_panel/translations/nl.json @@ -7,6 +7,13 @@ "disarm": "Uitschakelen {entity_name}", "trigger": "Trigger {entity_name}" }, + "condition_type": { + "is_armed_away": "{entity_name} afwezig ingeschakeld", + "is_armed_home": "{entity_name} thuis ingeschakeld", + "is_armed_night": "{entity_name} nachtstand ingeschakeld", + "is_disarmed": "{entity_name} is uitgeschakeld", + "is_triggered": "{entity_name} wordt geactiveerd" + }, "trigger_type": { "armed_away": "{entity_name} afwezig ingeschakeld", "armed_home": "{entity_name} thuis ingeschakeld", @@ -18,7 +25,7 @@ "state": { "_": { "armed": "Ingeschakeld", - "armed_away": "Ingeschakeld afwezig", + "armed_away": "Afwezig Ingeschakeld", "armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_home": "Ingeschakeld thuis", "armed_night": "Ingeschakeld nacht", diff --git a/homeassistant/components/atag/translations/fi.json b/homeassistant/components/atag/translations/fi.json new file mode 100644 index 00000000000..0483d8d2804 --- /dev/null +++ b/homeassistant/components/atag/translations/fi.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Palvelin", + "port": "Portti (10000)" + }, + "title": "Yhdist\u00e4 laitteeseen" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/hu.json b/homeassistant/components/atag/translations/hu.json new file mode 100644 index 00000000000..22687b6944a --- /dev/null +++ b/homeassistant/components/atag/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port (10000)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/sv.json b/homeassistant/components/atag/translations/sv.json new file mode 100644 index 00000000000..938a0191ee3 --- /dev/null +++ b/homeassistant/components/atag/translations/sv.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "connection_error": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen" + }, + "step": { + "user": { + "data": { + "host": "V\u00e4rd", + "port": "Port (10000)" + }, + "title": "Anslut till enheten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json new file mode 100644 index 00000000000..1697f634d9a --- /dev/null +++ b/homeassistant/components/august/translations/nl.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Account al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "login_method": "Aanmeldmethode", + "password": "Wachtwoord", + "timeout": "Time-out (seconden)", + "username": "Gebruikersnaam" + }, + "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" + }, + "validation": { + "data": { + "code": "Verificatiecode" + }, + "description": "Controleer je {login_method} ( {username} ) en voer de onderstaande verificatiecode in", + "title": "Tweestapsverificatie" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/sv.json b/homeassistant/components/august/translations/sv.json new file mode 100644 index 00000000000..df72f5daaf3 --- /dev/null +++ b/homeassistant/components/august/translations/sv.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Kontot har redan konfigurerats" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "login_method": "Inloggningsmetod", + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + }, + "validation": { + "title": "Tv\u00e5faktorsautentisering" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/it.json b/homeassistant/components/binary_sensor/translations/it.json index b0bb47b77d8..955fe525ad1 100644 --- a/homeassistant/components/binary_sensor/translations/it.json +++ b/homeassistant/components/binary_sensor/translations/it.json @@ -140,7 +140,7 @@ }, "opening": { "off": "Chiuso", - "on": "Aperta" + "on": "Aperto" }, "presence": { "off": "Fuori casa", diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index b5e59d830f8..ba09fbca3a3 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -20,7 +20,18 @@ "data": { "host": "Hostnaam of IP-adres van tv" }, - "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." + "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" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Lijst met genegeerde bronnen" + }, + "title": "Opties voor Sony Bravia TV" } } } diff --git a/homeassistant/components/braviatv/translations/ru.json b/homeassistant/components/braviatv/translations/ru.json index 33e8d71bd64..aa07f4d8fbc 100644 --- a/homeassistant/components/braviatv/translations/ru.json +++ b/homeassistant/components/braviatv/translations/ru.json @@ -20,7 +20,7 @@ "data": { "host": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 \u043f\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438:\nhttps://www.home-assistant.io/integrations/braviatv", + "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0435\u0439 \u043f\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438:\nhttps://www.home-assistant.io/integrations/braviatv", "title": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 Sony Bravia" } } diff --git a/homeassistant/components/braviatv/translations/sv.json b/homeassistant/components/braviatv/translations/sv.json new file mode 100644 index 00000000000..6ec160e799a --- /dev/null +++ b/homeassistant/components/braviatv/translations/sv.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Den h\u00e4r TV:n \u00e4r redan konfigurerad" + }, + "error": { + "invalid_host": "Ogiltigt v\u00e4rdnamn eller IP-adress.", + "unsupported_model": "Den h\u00e4r tv modellen st\u00f6ds inte." + }, + "step": { + "authorize": { + "data": { + "pin": "Pin-kod" + }, + "title": "Auktorisera Sony Bravia TV" + }, + "user": { + "data": { + "host": "V\u00e4rdnamn eller IP-adress f\u00f6r TV" + }, + "title": "Sony Bravia TV" + } + } + } +} \ 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 c33d4c06e6f..d844d28e62f 100644 --- a/homeassistant/components/cert_expiry/translations/nl.json +++ b/homeassistant/components/cert_expiry/translations/nl.json @@ -1,6 +1,11 @@ { "config": { + "abort": { + "already_configured": "Deze combinatie van host en poort is al geconfigureerd", + "import_failed": "Importeren vanuit configuratie is mislukt" + }, "error": { + "connection_refused": "Verbinding geweigerd bij verbinding met host", "connection_timeout": "Time-out bij verbinding maken met deze host", "resolve_failed": "Deze host kon niet gevonden worden" }, diff --git a/homeassistant/components/cert_expiry/translations/sv.json b/homeassistant/components/cert_expiry/translations/sv.json index 8449db1ec7a..23703f11e5b 100644 --- a/homeassistant/components/cert_expiry/translations/sv.json +++ b/homeassistant/components/cert_expiry/translations/sv.json @@ -1,6 +1,7 @@ { "config": { "error": { + "connection_refused": "Anslutningen blev tillbakavisad under anslutning till v\u00e4rd.", "connection_timeout": "Timeout vid anslutning till den h\u00e4r v\u00e4rden", "resolve_failed": "Denna v\u00e4rd kan inte resolveras" }, diff --git a/homeassistant/components/configurator/translations/it.json b/homeassistant/components/configurator/translations/it.json index 3e17f84d1c8..b8610b76d9d 100644 --- a/homeassistant/components/configurator/translations/it.json +++ b/homeassistant/components/configurator/translations/it.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Configura", + "configure": "Configurare", "configured": "Configurato" } }, diff --git a/homeassistant/components/coronavirus/translations/sv.json b/homeassistant/components/coronavirus/translations/sv.json new file mode 100644 index 00000000000..7e6686c2a04 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/sv.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Detta land \u00e4r redan konfigurerat." + }, + "step": { + "user": { + "data": { + "country": "Land" + }, + "title": "V\u00e4lj ett land att \u00f6vervaka" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/nl.json b/homeassistant/components/cover/translations/nl.json index f29132c3f18..7d68d78641e 100644 --- a/homeassistant/components/cover/translations/nl.json +++ b/homeassistant/components/cover/translations/nl.json @@ -1,5 +1,13 @@ { "device_automation": { + "action_type": { + "close": "Sluit {entity_name}", + "close_tilt": "Sluit de kanteling van {entity_name}", + "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" + }, "condition_type": { "is_closed": "{entity_name} is gesloten", "is_closing": "{entity_name} wordt gesloten", diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index 559581b8ad8..e7e0f5d917f 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -29,6 +29,12 @@ "host": "V\u00e4rd", "port": "Port (standardv\u00e4rde: '80')" } + }, + "manual_input": { + "data": { + "host": "V\u00e4rd", + "port": "Port" + } } } }, diff --git a/homeassistant/components/directv/translations/nl.json b/homeassistant/components/directv/translations/nl.json new file mode 100644 index 00000000000..b6635311064 --- /dev/null +++ b/homeassistant/components/directv/translations/nl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "DirecTV-ontvanger is al geconfigureerd", + "unknown": "Onverwachte fout" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "Wilt u {name} instellen?", + "title": "Maak verbinding met de DirecTV-ontvanger" + }, + "user": { + "data": { + "host": "Host- of IP-adres" + }, + "title": "Maak verbinding met de DirecTV-ontvanger" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/sv.json b/homeassistant/components/directv/translations/sv.json new file mode 100644 index 00000000000..c42c03d9944 --- /dev/null +++ b/homeassistant/components/directv/translations/sv.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "unknown": "Ov\u00e4ntat fel" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen" + }, + "step": { + "ssdp_confirm": { + "description": "Do vill du konfigurera {name}?" + }, + "user": { + "data": { + "host": "V\u00e4rd eller IP-adress" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index 36723f36dc0..625367484b0 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -1,13 +1,35 @@ { "config": { + "abort": { + "already_configured": "Deze DoorBird 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", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { + "host": "Host (IP-adres)", "name": "Apparaatnaam", "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Maak verbinding met de DoorBird" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Door komma's gescheiden lijst met gebeurtenissen." + }, + "description": "Voeg een door komma's gescheiden evenementnaam toe voor elk evenement dat u wilt volgen. Nadat je ze hier hebt ingevoerd, gebruik je de DoorBird-app om ze toe te wijzen aan een specifiek evenement. Zie de documentatie op https://www.home-assistant.io/integrations/doorbird/#events. Voorbeeld: iemand_drukte_knop, beweging" } } } diff --git a/homeassistant/components/doorbird/translations/sv.json b/homeassistant/components/doorbird/translations/sv.json new file mode 100644 index 00000000000..8025b956a17 --- /dev/null +++ b/homeassistant/components/doorbird/translations/sv.json @@ -0,0 +1,19 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "host": "V\u00e4rd (IP-adress)", + "name": "Enhetsnamn", + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/nl.json b/homeassistant/components/elkm1/translations/nl.json index 3f643bc67e7..9e7adf71c4b 100644 --- a/homeassistant/components/elkm1/translations/nl.json +++ b/homeassistant/components/elkm1/translations/nl.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "address_already_configured": "Een ElkM1 met dit adres is al geconfigureerd", + "already_configured": "Een ElkM1 met dit voorvoegsel is al geconfigureerd" + }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", "invalid_auth": "Ongeldige authenticatie", @@ -8,10 +12,15 @@ "step": { "user": { "data": { + "address": "Het IP-adres of domein of seri\u00eble poort bij verbinding via serieel.", "password": "Wachtwoord (alleen beveiligd).", + "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)." - } + }, + "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/elkm1/translations/sv.json b/homeassistant/components/elkm1/translations/sv.json new file mode 100644 index 00000000000..23a7d475a6f --- /dev/null +++ b/homeassistant/components/elkm1/translations/sv.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/en.json b/homeassistant/components/esphome/translations/en.json index 3f695e6f183..3cc24dea78e 100644 --- a/homeassistant/components/esphome/translations/en.json +++ b/homeassistant/components/esphome/translations/en.json @@ -32,4 +32,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/nl.json b/homeassistant/components/flume/translations/nl.json index 830bdc84e55..d176eb13365 100644 --- a/homeassistant/components/flume/translations/nl.json +++ b/homeassistant/components/flume/translations/nl.json @@ -11,10 +11,12 @@ "step": { "user": { "data": { + "client_id": "Client-id", "client_secret": "Client Secret", "password": "Wachtwoord", "username": "Gebruikersnaam" }, + "description": "Om toegang te krijgen tot de Flume Personal API, moet je een 'Client ID' en 'Client Secret' aanvragen op https://portal.flumetech.com/settings#token", "title": "Verbind met uw Flume account" } } diff --git a/homeassistant/components/flume/translations/sv.json b/homeassistant/components/flume/translations/sv.json new file mode 100644 index 00000000000..7bff51a3c83 --- /dev/null +++ b/homeassistant/components/flume/translations/sv.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Det h\u00e4r kontot har redan konfigurerats." + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "client_id": "Klient ID", + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/nl.json b/homeassistant/components/flunearyou/translations/nl.json index 3a71a79d137..c3f83fc93bf 100644 --- a/homeassistant/components/flunearyou/translations/nl.json +++ b/homeassistant/components/flunearyou/translations/nl.json @@ -11,7 +11,9 @@ "data": { "latitude": "Breedtegraad", "longitude": "Lengtegraad" - } + }, + "description": "Bewaak op gebruikers gebaseerde en CDC-repots voor een paar co\u00f6rdinaten.", + "title": "Configureer \nFlu Near You" } } } diff --git a/homeassistant/components/flunearyou/translations/sv.json b/homeassistant/components/flunearyou/translations/sv.json new file mode 100644 index 00000000000..adcf6008c1e --- /dev/null +++ b/homeassistant/components/flunearyou/translations/sv.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Dessa koordinater \u00e4r redan registrerade." + }, + "error": { + "general_error": "Ett ok\u00e4nt fel intr\u00e4ffade." + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/nl.json b/homeassistant/components/freebox/translations/nl.json new file mode 100644 index 00000000000..62c69997e17 --- /dev/null +++ b/homeassistant/components/freebox/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Host is al geconfigureerd." + }, + "error": { + "connection_failed": "Verbinding mislukt, probeer het opnieuw", + "register_failed": "Registratie is mislukt, probeer het opnieuw", + "unknown": "Onbekende fout: probeer het later nog eens" + }, + "step": { + "link": { + "description": "Klik op \"Verzenden\" en tik vervolgens op de rechterpijl op de router om Freebox te registreren bij Home Assistant. \n\n ! [Locatie van knop op de router] (/ static / images / config_freebox.png)", + "title": "Freebox-router koppelen" + }, + "user": { + "data": { + "host": "Host", + "port": "Poort" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/sv.json b/homeassistant/components/freebox/translations/sv.json new file mode 100644 index 00000000000..6c6cc5c64ec --- /dev/null +++ b/homeassistant/components/freebox/translations/sv.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "V\u00e4rden \u00e4r redan konfigurerad." + }, + "error": { + "connection_failed": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "register_failed": "Misslyckades med att registrera, v\u00e4nligen f\u00f6rs\u00f6k igen", + "unknown": "Ok\u00e4nt fel: f\u00f6rs\u00f6k igen senare" + }, + "step": { + "user": { + "data": { + "host": "V\u00e4rd", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 5f16553c64c..1a2e046d933 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Diese AVM FRITZ! Box ist bereits konfiguriert.", "already_in_progress": "Die Konfiguration der AVM FRITZ! Box ist bereits in Bearbeitung.", - "not_found": "Keine unterst\u00fctzte AVM FRITZ! Box im Netzwerk gefunden." + "not_found": "Keine unterst\u00fctzte AVM FRITZ! Box im Netzwerk gefunden.", + "not_supported": "Verbunden mit AVM FRITZ! Box, kann jedoch keine Smart Home-Ger\u00e4te steuern." }, "error": { "auth_failed": "Benutzername und/oder Passwort sind falsch." diff --git a/homeassistant/components/fritzbox/translations/fi.json b/homeassistant/components/fritzbox/translations/fi.json new file mode 100644 index 00000000000..bb4fb818a67 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/fi.json @@ -0,0 +1,19 @@ +{ + "config": { + "step": { + "confirm": { + "data": { + "password": "Salasana", + "username": "K\u00e4ytt\u00e4j\u00e4tunnus" + } + }, + "user": { + "data": { + "password": "Salasana", + "username": "K\u00e4ytt\u00e4j\u00e4tunnus" + }, + "title": "AVM FRITZ!Box" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index 3d8c94ab5b6..874f7add95e 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Deze AVM FRITZ!Box is al geconfigureerd.", "already_in_progress": "AVM FRITZ!Box configuratie is al bezig.", - "not_found": "Geen ondersteunde AVM FRITZ!Box gevonden op het netwerk." + "not_found": "Geen ondersteunde AVM FRITZ!Box gevonden op het netwerk.", + "not_supported": "Verbonden met AVM FRITZ! Box, maar het kan geen Smart Home-apparaten bedienen." }, "error": { "auth_failed": "Ongeldige gebruikersnaam of wachtwoord" diff --git a/homeassistant/components/fritzbox/translations/sv.json b/homeassistant/components/fritzbox/translations/sv.json new file mode 100644 index 00000000000..1ed4e4fc3d8 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/sv.json @@ -0,0 +1,23 @@ +{ + "config": { + "error": { + "auth_failed": "Anv\u00e4ndarnamn och/eller l\u00f6senord \u00e4r fel." + }, + "step": { + "confirm": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + }, + "description": "Do vill du konfigurera {name}?" + }, + "user": { + "data": { + "host": "V\u00e4rd eller IP-adress", + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/sv.json b/homeassistant/components/geonetnz_quakes/translations/sv.json index b1040e9bc23..feb654c267c 100644 --- a/homeassistant/components/geonetnz_quakes/translations/sv.json +++ b/homeassistant/components/geonetnz_quakes/translations/sv.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Plats \u00e4r redan konfigurerad." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/griddy/translations/nl.json b/homeassistant/components/griddy/translations/nl.json new file mode 100644 index 00000000000..9227d4702ab --- /dev/null +++ b/homeassistant/components/griddy/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Deze laadzone is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "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/sv.json b/homeassistant/components/griddy/translations/sv.json new file mode 100644 index 00000000000..e9ddacf2714 --- /dev/null +++ b/homeassistant/components/griddy/translations/sv.json @@ -0,0 +1,8 @@ +{ + "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/harmony/translations/nl.json b/homeassistant/components/harmony/translations/nl.json index a896cab0877..63d8026d9c2 100644 --- a/homeassistant/components/harmony/translations/nl.json +++ b/homeassistant/components/harmony/translations/nl.json @@ -4,7 +4,8 @@ "already_configured": "Apparaat is al geconfigureerd" }, "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" }, "flow_title": "Logitech Harmony Hub {name}", "step": { @@ -20,5 +21,16 @@ "title": "Logitech Harmony Hub instellen" } } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "De standaardactiviteit die moet worden uitgevoerd wanneer er geen is opgegeven.", + "delay_secs": "De vertraging tussen het verzenden van opdrachten." + }, + "description": "Pas de Harmony Hub-opties aan" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/sv.json b/homeassistant/components/harmony/translations/sv.json new file mode 100644 index 00000000000..6e9c861763b --- /dev/null +++ b/homeassistant/components/harmony/translations/sv.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "link": { + "description": "Do vill du konfigurera {name} ({host})?" + }, + "user": { + "data": { + "host": "V\u00e4rdnamn eller IP-adress" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 493809dca07..bfe70ae53ab 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -33,6 +33,7 @@ "button_2": "Tweede knop", "button_3": "Derde knop", "button_4": "Vierde knop", + "dim_down": "Dim omlaag", "dim_up": "Dim omhoog", "double_buttons_1_3": "Eerste en derde knop", "double_buttons_2_4": "Tweede en vierde knop", diff --git a/homeassistant/components/hue/translations/sv.json b/homeassistant/components/hue/translations/sv.json index 894c4f9f988..80f7b179692 100644 --- a/homeassistant/components/hue/translations/sv.json +++ b/homeassistant/components/hue/translations/sv.json @@ -26,5 +26,22 @@ "title": "L\u00e4nka hub" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "F\u00f6rsta knappen", + "button_2": "Andra knappen", + "button_3": "Tredje knappen", + "button_4": "Fj\u00e4rde knappen", + "dim_down": "Dimma ned", + "dim_up": "Dimma upp", + "turn_off": "St\u00e4ng av", + "turn_on": "Starta" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" knappen sl\u00e4pptes efter ett l\u00e5ngt tryck", + "remote_button_short_press": "\"{subtype}\" knappen nedtryckt", + "remote_button_short_release": "\"{subtype}\"-knappen sl\u00e4ppt" + } } } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/de.json b/homeassistant/components/hunterdouglas_powerview/translations/de.json new file mode 100644 index 00000000000..50dc39b3d98 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "unknown": "Unerwarteter Fehler" + }, + "step": { + "link": { + "description": "M\u00f6chten Sie {name} ({host}) einrichten?", + "title": "Stellen Sie eine Verbindung zum PowerView Hub her" + }, + "user": { + "data": { + "host": "IP-Adresse" + }, + "title": "Stellen Sie eine Verbindung zum PowerView Hub her" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/es.json b/homeassistant/components/hunterdouglas_powerview/translations/es.json new file mode 100644 index 00000000000..46e216976bd --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/es.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo", + "unknown": "Error inesperado" + }, + "step": { + "link": { + "description": "\u00bfQuieres configurar {name} ({host})?", + "title": "Conectar con el PowerView Hub" + }, + "user": { + "data": { + "host": "Direcci\u00f3n IP" + }, + "title": "Conectar con el PowerView Hub" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/fi.json b/homeassistant/components/hunterdouglas_powerview/translations/fi.json new file mode 100644 index 00000000000..86ad64c1692 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/fi.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Laite on jo m\u00e4\u00e4ritetty" + }, + "error": { + "cannot_connect": "Yhteyden muodostaminen ep\u00e4onnistui. Yrit\u00e4 uudelleen", + "unknown": "Odottamaton virhe" + }, + "step": { + "user": { + "data": { + "host": "IP-osoite" + }, + "title": "Yhdist\u00e4 PowerView-keskittimeen" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/hu.json b/homeassistant/components/hunterdouglas_powerview/translations/hu.json new file mode 100644 index 00000000000..baa3b135d42 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "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" + }, + "step": { + "user": { + "data": { + "host": "IP-c\u00edm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/it.json b/homeassistant/components/hunterdouglas_powerview/translations/it.json new file mode 100644 index 00000000000..2dcb2a72c8b --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/it.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "unknown": "Errore imprevisto" + }, + "step": { + "link": { + "description": "Vuoi impostare {name} ({host})?", + "title": "Connettersi all'Hub PowerView" + }, + "user": { + "data": { + "host": "Indirizzo IP" + }, + "title": "Collegamento al PowerView Hub" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/nl.json b/homeassistant/components/hunterdouglas_powerview/translations/nl.json new file mode 100644 index 00000000000..9c0ab4932de --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/nl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" + }, + "step": { + "link": { + "description": "Wil je {name} ({host}) instellen?", + "title": "Maak verbinding met de PowerView Hub" + }, + "user": { + "data": { + "host": "IP-adres" + }, + "title": "Maak verbinding met de PowerView Hub" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/ru.json b/homeassistant/components/hunterdouglas_powerview/translations/ru.json new file mode 100644 index 00000000000..88d363c63da --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/ru.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "link": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", + "title": "Hunter Douglas PowerView" + }, + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441" + }, + "title": "Hunter Douglas PowerView" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/sv.json b/homeassistant/components/hunterdouglas_powerview/translations/sv.json new file mode 100644 index 00000000000..04371b16514 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/sv.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "link": { + "description": "Do vill du konfigurera {name} ({host})?", + "title": "Anslut till PowerView Hub" + }, + "user": { + "data": { + "host": "IP-adress" + }, + "title": "Anslut till PowerView Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/zh-Hant.json b/homeassistant/components/hunterdouglas_powerview/translations/zh-Hant.json new file mode 100644 index 00000000000..0eb5a4d3d37 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/zh-Hant.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "link": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f", + "title": "\u9023\u7dda\u81f3 PowerView Hub" + }, + "user": { + "data": { + "host": "IP \u4f4d\u5740" + }, + "title": "\u9023\u7dda\u81f3 PowerView Hub" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/nl.json b/homeassistant/components/icloud/translations/nl.json index 84691ebd134..ee1e20c6969 100644 --- a/homeassistant/components/icloud/translations/nl.json +++ b/homeassistant/components/icloud/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account reeds geconfigureerd" + "already_configured": "Account reeds geconfigureerd", + "no_device": "Op geen van uw apparaten is \"Find my iPhone\" geactiveerd" }, "error": { "login": "Aanmeldingsfout: controleer uw e-mailadres en wachtwoord", @@ -19,7 +20,8 @@ "user": { "data": { "password": "Wachtwoord", - "username": "E-mail" + "username": "E-mail", + "with_family": "Met gezin" }, "description": "Voer uw gegevens in", "title": "iCloud inloggegevens" diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index e775d869615..12f9d37ec7a 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -7,6 +7,28 @@ "ipp_error": "Er is een IPP-fout opgetreden.", "ipp_version_error": "IPP-versie wordt niet ondersteund door printer.", "parse_error": "Ongeldige reactie van de printer." + }, + "error": { + "connection_error": "Kan geen verbinding maken met de printer.", + "connection_upgrade": "Kan geen verbinding maken met de printer. Probeer het opnieuw met SSL / TLS-optie aangevinkt." + }, + "flow_title": "Printer: {name}", + "step": { + "user": { + "data": { + "base_path": "Relatief pad naar de printer", + "host": "Host- of IP-adres", + "port": "Poort", + "ssl": "Printer ondersteunt communicatie via SSL / TLS", + "verify_ssl": "Printer gebruikt een correct SSL-certificaat" + }, + "description": "Stel uw printer in via Internet Printing Protocol (IPP) om te integreren met Home Assistant.", + "title": "Koppel uw printer" + }, + "zeroconf_confirm": { + "description": "Wilt u de printer met de naam ' {name} ' toevoegen aan Home Assistant?", + "title": "Gedetecteerde printer" + } } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/ru.json b/homeassistant/components/ipp/translations/ru.json index 5a8c6068769..eee7de36cd1 100644 --- a/homeassistant/components/ipp/translations/ru.json +++ b/homeassistant/components/ipp/translations/ru.json @@ -22,7 +22,7 @@ "ssl": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u0432\u044f\u0437\u044c \u043f\u043e SSL/TLS", "verify_ssl": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 \u043f\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 IPP \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Home Assistant.", + "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 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043f\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 IPP.", "title": "Internet Printing Protocol (IPP)" }, "zeroconf_confirm": { diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 45351cd0966..17bb20be765 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -8,6 +8,10 @@ "confirm": { "title": "Konnected Apparaat Klaar" }, + "import_confirm": { + "description": "Er is een Konnected Alarmpaneel met ID {id} ontdekt in configuration.yaml. Met deze flow kunt u deze importeren in een configuratie-item.", + "title": "Konnected apparaat importeren" + }, "user": { "data": { "host": "IP-adres van Konnected apparaat", @@ -23,6 +27,7 @@ "not_konn_panel": "Geen herkend Konnected.io apparaat" }, "error": { + "bad_host": "Ongeldige URL voor overschrijven API-host", "one": "Leeg", "other": "Leeg" }, @@ -58,13 +63,19 @@ "11": "Zone 11", "12": "Zone 12", "8": "Zone 8", - "9": "Zone 9" + "9": "Zone 9", + "alarm1": "ALARM1", + "alarm2_out2": "OUT2 / ALARM2", + "out1": "OUT1" } }, "options_misc": { "data": { - "api_host": "API host-URL overschrijven (optioneel)" - } + "api_host": "API host-URL overschrijven (optioneel)", + "override_api_host": "Overschrijf standaard Home Assistant API hostpaneel-URL" + }, + "description": "Selecteer het gewenste gedrag voor uw paneel", + "title": "Configureer Misc" }, "options_switch": { "data": { diff --git a/homeassistant/components/light/translations/it.json b/homeassistant/components/light/translations/it.json index c9cc397211d..9477171c9f6 100644 --- a/homeassistant/components/light/translations/it.json +++ b/homeassistant/components/light/translations/it.json @@ -19,8 +19,8 @@ }, "state": { "_": { - "off": "Spento", - "on": "Acceso" + "off": "Spenta", + "on": "Accesa" } }, "title": "Luce" diff --git a/homeassistant/components/light/translations/nl.json b/homeassistant/components/light/translations/nl.json index 5c4dc969b0f..190ed3f52bd 100644 --- a/homeassistant/components/light/translations/nl.json +++ b/homeassistant/components/light/translations/nl.json @@ -1,6 +1,8 @@ { "device_automation": { "action_type": { + "brightness_decrease": "Verlaag de helderheid van {entity_name}", + "brightness_increase": "Verhoog de helderheid van {entity_name}", "flash": "Flash {entity_name}", "toggle": "Omschakelen {entity_name}", "turn_off": "{entity_name} uitschakelen", diff --git a/homeassistant/components/local_ip/translations/nl.json b/homeassistant/components/local_ip/translations/nl.json index 88de9704a6e..ba75a9b2a4d 100644 --- a/homeassistant/components/local_ip/translations/nl.json +++ b/homeassistant/components/local_ip/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van lokaal IP toegestaan." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/local_ip/translations/sv.json b/homeassistant/components/local_ip/translations/sv.json index 9c8f27dff8d..d4b508b41a7 100644 --- a/homeassistant/components/local_ip/translations/sv.json +++ b/homeassistant/components/local_ip/translations/sv.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Endast en konfiguration av lokal IP \u00e4r till\u00e5ten." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/monoprice/translations/nl.json b/homeassistant/components/monoprice/translations/nl.json index 8b8126a63ee..74bc677dbe8 100644 --- a/homeassistant/components/monoprice/translations/nl.json +++ b/homeassistant/components/monoprice/translations/nl.json @@ -10,7 +10,13 @@ "step": { "user": { "data": { - "port": "Seri\u00eble poort" + "port": "Seri\u00eble poort", + "source_1": "Naam van bron #1", + "source_2": "Naam van bron #2", + "source_3": "Naam van bron #3", + "source_4": "Naam van bron #4", + "source_5": "Naam van bron #5", + "source_6": "Naam van bron #6" }, "title": "Verbinding maken met het apparaat" } diff --git a/homeassistant/components/monoprice/translations/sv.json b/homeassistant/components/monoprice/translations/sv.json new file mode 100644 index 00000000000..3531253b136 --- /dev/null +++ b/homeassistant/components/monoprice/translations/sv.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "title": "Anslut till enheten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 56684b32535..f7c099c2cb6 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -26,5 +26,16 @@ "title": "MQTTT Broker via Hass.io add-on" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Eerste knop", + "button_2": "Tweede knop", + "button_3": "Derde knop", + "button_4": "Vierde knop", + "button_5": "Vijfde knop", + "button_6": "Zesde knop", + "turn_off": "Uitschakelen" + } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/sv.json b/homeassistant/components/mqtt/translations/sv.json index 58855eeb62b..9415beedbf7 100644 --- a/homeassistant/components/mqtt/translations/sv.json +++ b/homeassistant/components/mqtt/translations/sv.json @@ -26,5 +26,17 @@ "title": "MQTT Broker via Hass.io till\u00e4gg" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "F\u00f6rsta knappen", + "button_2": "Andra knappen", + "button_3": "Tredje knappen", + "button_4": "Fj\u00e4rde knappen", + "button_5": "Femte knappen", + "button_6": "Sj\u00e4tte knappen", + "turn_off": "St\u00e4ng av", + "turn_on": "Starta" + } } } \ No newline at end of file diff --git a/homeassistant/components/myq/translations/sv.json b/homeassistant/components/myq/translations/sv.json new file mode 100644 index 00000000000..1243ca600f0 --- /dev/null +++ b/homeassistant/components/myq/translations/sv.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/nl.json b/homeassistant/components/nexia/translations/nl.json index 8767b93d173..d718c78d7af 100644 --- a/homeassistant/components/nexia/translations/nl.json +++ b/homeassistant/components/nexia/translations/nl.json @@ -1,5 +1,13 @@ { "config": { + "abort": { + "already_configured": "Deze nexia-woning is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/nexia/translations/sv.json b/homeassistant/components/nexia/translations/sv.json new file mode 100644 index 00000000000..9cfd620ac73 --- /dev/null +++ b/homeassistant/components/nexia/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/nl.json b/homeassistant/components/notion/translations/nl.json index b6a28495692..473659c0eb2 100644 --- a/homeassistant/components/notion/translations/nl.json +++ b/homeassistant/components/notion/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Deze gebruikersnaam is al in gebruik." + }, "error": { "invalid_credentials": "Ongeldige gebruikersnaam of wachtwoord", "no_devices": "Geen apparaten gevonden in account" diff --git a/homeassistant/components/notion/translations/sv.json b/homeassistant/components/notion/translations/sv.json index 6f9ab3bac1c..7e05095cf0e 100644 --- a/homeassistant/components/notion/translations/sv.json +++ b/homeassistant/components/notion/translations/sv.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Det h\u00e4r anv\u00e4ndarnamnet anv\u00e4nds redan." + }, "error": { "invalid_credentials": "Felaktigt anv\u00e4ndarnamn eller l\u00f6senord", "no_devices": "Inga enheter hittades p\u00e5 kontot" diff --git a/homeassistant/components/nuheat/translations/nl.json b/homeassistant/components/nuheat/translations/nl.json new file mode 100644 index 00000000000..edf3ad17ff4 --- /dev/null +++ b/homeassistant/components/nuheat/translations/nl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "De thermostaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "invalid_thermostat": "Het serienummer van de thermostaat is ongeldig.", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "serial_number": "Serienummer van de thermostaat.", + "username": "Gebruikersnaam" + }, + "description": "U moet het numerieke serienummer of de ID van uw thermostaat verkrijgen door in te loggen op https://MyNuHeat.com en uw thermostaat (thermostaten) te selecteren.", + "title": "Maak verbinding met de NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/sv.json b/homeassistant/components/nuheat/translations/sv.json new file mode 100644 index 00000000000..9cfd620ac73 --- /dev/null +++ b/homeassistant/components/nuheat/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/nl.json b/homeassistant/components/nut/translations/nl.json index d0ed6c3c573..5e4acf3574d 100644 --- a/homeassistant/components/nut/translations/nl.json +++ b/homeassistant/components/nut/translations/nl.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", "unknown": "Onverwachte fout" }, "step": { @@ -11,6 +15,10 @@ "title": "Kies de te controleren bronnen" }, "ups": { + "data": { + "alias": "Alias", + "resources": "Bronnen" + }, "title": "Kies een UPS om uit te lezen" }, "user": { @@ -28,8 +36,10 @@ "step": { "init": { "data": { - "resources": "Bronnen" - } + "resources": "Bronnen", + "scan_interval": "Scaninterval (seconden)" + }, + "description": "Kies Sensorbronnen." } } } diff --git a/homeassistant/components/nut/translations/sv.json b/homeassistant/components/nut/translations/sv.json new file mode 100644 index 00000000000..eb839d31fb2 --- /dev/null +++ b/homeassistant/components/nut/translations/sv.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "resources": { + "data": { + "resources": "Resurser" + } + }, + "user": { + "data": { + "host": "V\u00e4rd", + "password": "L\u00f6senord", + "port": "Port", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/fi.json b/homeassistant/components/nws/translations/fi.json new file mode 100644 index 00000000000..5c3329a260a --- /dev/null +++ b/homeassistant/components/nws/translations/fi.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "unknown": "Odottamaton virhe" + }, + "step": { + "user": { + "data": { + "api_key": "API-avain (s\u00e4hk\u00f6posti)", + "latitude": "Leveysaste", + "longitude": "Pituusaste" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/nl.json b/homeassistant/components/nws/translations/nl.json index 532ec589a65..b74e6db96a2 100644 --- a/homeassistant/components/nws/translations/nl.json +++ b/homeassistant/components/nws/translations/nl.json @@ -1,12 +1,22 @@ { "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" + }, "step": { "user": { "data": { "api_key": "API-sleutel (e-mail)", "latitude": "Breedtegraad", - "longitude": "Lengtegraad" - } + "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.", + "title": "Maak verbinding met de National Weather Service" } } } diff --git a/homeassistant/components/nws/translations/sv.json b/homeassistant/components/nws/translations/sv.json new file mode 100644 index 00000000000..fefa01fd40a --- /dev/null +++ b/homeassistant/components/nws/translations/sv.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/fi.json b/homeassistant/components/panasonic_viera/translations/fi.json new file mode 100644 index 00000000000..22e861af227 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/fi.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "unknown": "Tapahtui tuntematon virhe. Lis\u00e4tietoja on lokeissa." + }, + "error": { + "invalid_pin_code": "Antamasi PIN-koodi ei kelpaa" + }, + "step": { + "pairing": { + "data": { + "pin": "PIN" + }, + "description": "Kirjoita televisiossa n\u00e4kyv\u00e4 PIN-koodi", + "title": "Paritus" + }, + "user": { + "data": { + "host": "IP-osoite", + "name": "Nimi" + }, + "description": "Anna Panasonic Viera TV:n IP-osoite", + "title": "Television m\u00e4\u00e4ritt\u00e4minen" + } + } + }, + "title": "Panasonic Viera" +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/sv.json b/homeassistant/components/panasonic_viera/translations/sv.json new file mode 100644 index 00000000000..f70336fae9f --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/sv.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "unknown": "Ett ov\u00e4ntat fel intr\u00e4ffade. Kontrollera loggarna f\u00f6r mer information." + }, + "step": { + "user": { + "data": { + "host": "IP-adress", + "name": "Namn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/nl.json b/homeassistant/components/powerwall/translations/nl.json new file mode 100644 index 00000000000..f77cc864813 --- /dev/null +++ b/homeassistant/components/powerwall/translations/nl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "De powerwall is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "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." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adres" + }, + "title": "Maak verbinding met de powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/sv.json b/homeassistant/components/powerwall/translations/sv.json new file mode 100644 index 00000000000..0c6f94cd697 --- /dev/null +++ b/homeassistant/components/powerwall/translations/sv.json @@ -0,0 +1,16 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel", + "wrong_version": "Powerwall anv\u00e4nder en programvaruversion som inte st\u00f6ds. T\u00e4nk p\u00e5 att uppgradera eller rapportera det h\u00e4r problemet s\u00e5 att det kan l\u00f6sas." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adress" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json index c489bef5a2c..3abffdf5bc0 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/nl.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/nl.json @@ -1,10 +1,16 @@ { "config": { + "abort": { + "already_configured": "Integratie is al geconfigureerd met een bestaande sensor met dat tarief" + }, "step": { "user": { "data": { - "name": "Sensornaam" - } + "name": "Sensornaam", + "tariff": "Gecontracteerd tarief (1, 2 of 3 periodes)" + }, + "description": "Deze sensor gebruikt de offici\u00eble API om [uurtarief voor elektriciteit (PVPC)] (https://www.esios.ree.es/es/pvpc) in Spanje te krijgen. \n Bezoek voor een meer precieze uitleg de [integratiedocumenten] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n Selecteer het gecontracteerde tarief op basis van het aantal factureringsperioden per dag: \n - 1 periode: normaal \n - 2 periodes: discriminatie (nachttarief) \n - 3 periodes: elektrische auto (nachttarief van 3 periodes)", + "title": "Tariefselectie" } } } diff --git a/homeassistant/components/rachio/translations/nl.json b/homeassistant/components/rachio/translations/nl.json new file mode 100644 index 00000000000..2173c768185 --- /dev/null +++ b/homeassistant/components/rachio/translations/nl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "api_key": "De API-sleutel voor het Rachio-account." + }, + "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" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "Hoe lang, in minuten, om een station in te schakelen wanneer de schakelaar is ingeschakeld." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/sv.json b/homeassistant/components/rachio/translations/sv.json new file mode 100644 index 00000000000..726912f62f9 --- /dev/null +++ b/homeassistant/components/rachio/translations/sv.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/nl.json b/homeassistant/components/rainmachine/translations/nl.json index 545d0ded465..3d13a48712b 100644 --- a/homeassistant/components/rainmachine/translations/nl.json +++ b/homeassistant/components/rainmachine/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Deze RainMachine controller is al geconfigureerd." + }, "error": { "identifier_exists": "Account bestaat al", "invalid_credentials": "Ongeldige gebruikersgegevens" diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json new file mode 100644 index 00000000000..8f2fae7146e --- /dev/null +++ b/homeassistant/components/roku/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Roku-apparaat is al geconfigureerd", + "unknown": "Onverwachte fout" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw" + }, + "flow_title": "Roku: {name}", + "step": { + "ssdp_confirm": { + "description": "Wilt u {name} instellen?", + "title": "Roku" + }, + "user": { + "data": { + "host": "Host- of IP-adres" + }, + "description": "Voer uw Roku-informatie in.", + "title": "Roku" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/sv.json b/homeassistant/components/roku/translations/sv.json new file mode 100644 index 00000000000..6d6f9223466 --- /dev/null +++ b/homeassistant/components/roku/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "unknown": "Ov\u00e4ntat fel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json new file mode 100644 index 00000000000..d49a9f488de --- /dev/null +++ b/homeassistant/components/roomba/translations/nl.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "certificate": "Certificaat", + "continuous": "Doorlopend", + "delay": "Vertraging", + "host": "Hostnaam of IP-adres", + "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", + "title": "Verbinding maken met het apparaat" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Doorlopend", + "delay": "Vertraging" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/sv.json b/homeassistant/components/roomba/translations/sv.json new file mode 100644 index 00000000000..f39fd27b705 --- /dev/null +++ b/homeassistant/components/roomba/translations/sv.json @@ -0,0 +1,29 @@ +{ + "config": { + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "certificate": "Certifikat", + "continuous": "Kontinuerlig", + "delay": "F\u00f6rdr\u00f6jning", + "host": "V\u00e4rdnamn eller IP-adress", + "password": "L\u00f6senord" + }, + "title": "Anslut till enheten" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "delay": "F\u00f6rdr\u00f6jning" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.fi.json b/homeassistant/components/season/translations/sensor.fi.json index f01f6451549..012735e07f8 100644 --- a/homeassistant/components/season/translations/sensor.fi.json +++ b/homeassistant/components/season/translations/sensor.fi.json @@ -1,8 +1,10 @@ { "state": { - "autumn": "Syksy", - "spring": "Kev\u00e4t", - "summer": "Kes\u00e4", - "winter": "Talvi" + "season__season": { + "autumn": "Syksy", + "spring": "Kev\u00e4t", + "summer": "Kes\u00e4", + "winter": "Talvi" + } } } \ No newline at end of file diff --git a/homeassistant/components/season/translations/sensor.sv.json b/homeassistant/components/season/translations/sensor.sv.json index 59875084928..bffbdaa8d2f 100644 --- a/homeassistant/components/season/translations/sensor.sv.json +++ b/homeassistant/components/season/translations/sensor.sv.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "H\u00f6st", + "spring": "V\u00e5r", + "summer": "Sommar", + "winter": "Vinter" + }, "season__season__": { "autumn": "H\u00f6st", "spring": "V\u00e5r", diff --git a/homeassistant/components/sense/translations/nl.json b/homeassistant/components/sense/translations/nl.json new file mode 100644 index 00000000000..ee9e61b5a38 --- /dev/null +++ b/homeassistant/components/sense/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "email": "E-mailadres", + "password": "Wachtwoord" + }, + "title": "Maak verbinding met uw Sense Energy Monitor" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/sv.json b/homeassistant/components/sense/translations/sv.json new file mode 100644 index 00000000000..02939a27dbb --- /dev/null +++ b/homeassistant/components/sense/translations/sv.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "email": "E-postadress", + "password": "L\u00f6senord" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/nl.json b/homeassistant/components/shopping_list/translations/nl.json new file mode 100644 index 00000000000..de6045dd81b --- /dev/null +++ b/homeassistant/components/shopping_list/translations/nl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "De Shopping List is al geconfigureerd." + }, + "step": { + "user": { + "description": "Wil je de Shopping List configureren?", + "title": "Shopping List" + } + } + }, + "title": "Shopping List" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index 2c5a0d0971c..42fc575f650 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Code (wird in der Benutzeroberfl\u00e4che von Home Assistant verwendet)", "password": "Passwort", "username": "E-Mail-Adresse" }, diff --git a/homeassistant/components/simplisafe/translations/nl.json b/homeassistant/components/simplisafe/translations/nl.json index f585a9c9231..ce3fcf2902f 100644 --- a/homeassistant/components/simplisafe/translations/nl.json +++ b/homeassistant/components/simplisafe/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Dit SimpliSafe-account is al in gebruik." + }, "error": { "identifier_exists": "Account bestaat al", "invalid_credentials": "Ongeldige gebruikersgegevens" @@ -14,5 +17,15 @@ "title": "Vul uw gegevens in" } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "Code (gebruikt in de Home Assistant UI)" + }, + "title": "Configureer SimpliSafe" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/nl.json b/homeassistant/components/smartthings/translations/nl.json index 85a0ed4a736..a77ad40f0ca 100644 --- a/homeassistant/components/smartthings/translations/nl.json +++ b/homeassistant/components/smartthings/translations/nl.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "invalid_webhook_url": "Home Assistant is niet correct geconfigureerd om updates van SmartThings te ontvangen. De webhook-URL is ongeldig: \n > {webhook_url} \n\n Werk uw configuratie bij volgens de [instructies] ( {component_url} ), start de Home Assistant opnieuw op en probeer het opnieuw.", + "no_available_locations": "Er zijn geen beschikbare SmartThings-locaties om in te stellen in Home Assistant." + }, "error": { "app_setup_error": "Instellen van SmartApp mislukt. Probeer het opnieuw.", "token_forbidden": "Het token heeft niet de vereiste OAuth-scopes.", @@ -15,12 +19,14 @@ "data": { "access_token": "Toegangstoken" }, + "description": "Voer een SmartThings [Personal Access Token] ( {token_url} ) in dat is gemaakt volgens de [instructies] ( {component_url} ). Dit wordt gebruikt om de Home Assistant-integratie te cre\u00ebren binnen uw SmartThings-account.", "title": "Persoonlijk toegangstoken invoeren" }, "select_location": { "data": { "location_id": "Locatie" }, + "description": "Selecteer de SmartThings-locatie die u aan de Home Assistant wilt toevoegen. We zullen dan een nieuw venster openen en u vragen om in te loggen en de installatie van de Home Assistant-integratie op de geselecteerde locatie te autoriseren.", "title": "Locatie selecteren" }, "user": { diff --git a/homeassistant/components/smartthings/translations/sv.json b/homeassistant/components/smartthings/translations/sv.json index a02483baa47..1b1f8859298 100644 --- a/homeassistant/components/smartthings/translations/sv.json +++ b/homeassistant/components/smartthings/translations/sv.json @@ -8,6 +8,20 @@ "webhook_error": "SmartThings kunde inte validera endpoint konfigurerad i \" base_url`. V\u00e4nligen granska kraven f\u00f6r komponenten." }, "step": { + "authorize": { + "title": "Auktorisera Home Assistant" + }, + "pat": { + "data": { + "access_token": "\u00c5tkomstnyckel" + } + }, + "select_location": { + "data": { + "location_id": "Position" + }, + "title": "V\u00e4lj plats" + }, "user": { "description": "V\u00e4nligen ange en [personlig \u00e5tkomsttoken]({token_url}) f\u00f6r SmartThings som har skapats enligt [instruktionerna]({component_url}).", "title": "Ange personlig \u00e5tkomsttoken" diff --git a/homeassistant/components/synology_dsm/translations/fi.json b/homeassistant/components/synology_dsm/translations/fi.json new file mode 100644 index 00000000000..19cf9e272cf --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/fi.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "otp_failed": "Kaksivaiheinen todennus ep\u00e4onnistui, yrit\u00e4 uudelleen uudella salasanalla." + }, + "step": { + "2sa": { + "data": { + "otp_code": "Koodi" + } + } + } + } +} \ 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 5e2fa85fb52..8b69639140b 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -6,6 +6,7 @@ "error": { "connection": "Verbindingsfout: controleer uw host, poort & ssl", "login": "Aanmeldingsfout: controleer uw gebruikersnaam en wachtwoord", + "missing_data": "Ontbrekende gegevens: probeer het later opnieuw of een andere configuratie", "otp_failed": "Tweestapsverificatie is mislukt, probeer het opnieuw met een nieuwe toegangscode", "unknown": "Onbekende fout: controleer de logs voor meer informatie" }, diff --git a/homeassistant/components/synology_dsm/translations/sv.json b/homeassistant/components/synology_dsm/translations/sv.json new file mode 100644 index 00000000000..c0bdc759122 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/sv.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "V\u00e4rden \u00e4r redan konfigurerad." + }, + "error": { + "connection": "Anslutningsfel: v\u00e4nligen kontrollera v\u00e4rd, port & SSL" + }, + "step": { + "link": { + "data": { + "password": "L\u00f6senord", + "port": "Port (Valfri)", + "username": "Anv\u00e4ndarnamn" + }, + "description": "Do vill du konfigurera {name} ({host})?" + }, + "user": { + "data": { + "host": "V\u00e4rd", + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/nl.json b/homeassistant/components/tado/translations/nl.json index 5099637a637..3cdadf0f54e 100644 --- a/homeassistant/components/tado/translations/nl.json +++ b/homeassistant/components/tado/translations/nl.json @@ -1,10 +1,32 @@ { + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie", + "no_homes": "Er zijn geen huizen gekoppeld aan dit tado-account.", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Maak verbinding met je Tado-account" + } + } + }, "options": { "step": { "init": { "data": { "fallback": "Schakel de terugvalmodus in." - } + }, + "description": "De fallback-modus schakelt over naar Smart Schedule bij de volgende schemaschakeling na het handmatig aanpassen van een zone.", + "title": "Pas Tado-opties aan." } } } diff --git a/homeassistant/components/tado/translations/sv.json b/homeassistant/components/tado/translations/sv.json new file mode 100644 index 00000000000..b00dc6e93b2 --- /dev/null +++ b/homeassistant/components/tado/translations/sv.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/nl.json b/homeassistant/components/tesla/translations/nl.json index 02bbd32e417..27df1acae2a 100644 --- a/homeassistant/components/tesla/translations/nl.json +++ b/homeassistant/components/tesla/translations/nl.json @@ -21,6 +21,7 @@ "step": { "init": { "data": { + "enable_wake_on_start": "Forceer auto's wakker bij het opstarten", "scan_interval": "Seconden tussen scans" } } diff --git a/homeassistant/components/timer/translations/it.json b/homeassistant/components/timer/translations/it.json index 464a2feb501..b5627e51d0b 100644 --- a/homeassistant/components/timer/translations/it.json +++ b/homeassistant/components/timer/translations/it.json @@ -1,9 +1,9 @@ { "state": { "_": { - "active": "attivo", - "idle": "inattivo", - "paused": "in pausa" + "active": "Attivo", + "idle": "Inattivo", + "paused": "In pausa" } } } \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/nl.json b/homeassistant/components/totalconnect/translations/nl.json index 3196a58675c..508c112ae61 100644 --- a/homeassistant/components/totalconnect/translations/nl.json +++ b/homeassistant/components/totalconnect/translations/nl.json @@ -11,7 +11,8 @@ "data": { "password": "Wachtwoord", "username": "Gebruikersnaam" - } + }, + "title": "Total Connect" } } } diff --git a/homeassistant/components/totalconnect/translations/sv.json b/homeassistant/components/totalconnect/translations/sv.json new file mode 100644 index 00000000000..68dc9efeedb --- /dev/null +++ b/homeassistant/components/totalconnect/translations/sv.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "Kontot har redan konfigurerats" + }, + "error": { + "login": "Inloggningsfel: v\u00e4nligen kontrollera ditt anv\u00e4ndarnamn och l\u00f6senord" + }, + "step": { + "user": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 62611d0aac8..e37018350cb 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -6,7 +6,8 @@ }, "error": { "faulty_credentials": "Foutieve gebruikersgegevens", - "service_unavailable": "Geen service beschikbaar" + "service_unavailable": "Geen service beschikbaar", + "unknown_client_mac": "Geen client beschikbaar op dat MAC-adres" }, "step": { "user": { @@ -26,8 +27,12 @@ "step": { "client_control": { "data": { + "block_client": "Cli\u00ebnten met netwerktoegang", + "new_client": "Voeg een nieuwe client toe voor netwerktoegangsbeheer", "poe_clients": "Sta POE-controle van gebruikers toe" - } + }, + "description": "Configureer clientbesturingen \n\n Maak schakelaars voor serienummers waarvoor u de netwerktoegang wilt beheren.", + "title": "UniFi-opties 2/3" }, "device_tracker": { "data": { @@ -36,7 +41,9 @@ "track_clients": "Volg netwerkclients", "track_devices": "Netwerkapparaten volgen (Ubiquiti-apparaten)", "track_wired_clients": "Inclusief bedrade netwerkcli\u00ebnten" - } + }, + "description": "Apparaattracking configureren", + "title": "UniFi-opties 1/3" }, "init": { "data": { @@ -47,7 +54,8 @@ "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Maak bandbreedtegebruiksensoren voor netwerkclients" - } + }, + "title": "UniFi-opties 3/3" } } } diff --git a/homeassistant/components/unifi/translations/sv.json b/homeassistant/components/unifi/translations/sv.json index a41503b5ea2..d3dd18a28e0 100644 --- a/homeassistant/components/unifi/translations/sv.json +++ b/homeassistant/components/unifi/translations/sv.json @@ -6,7 +6,8 @@ }, "error": { "faulty_credentials": "Felaktiga anv\u00e4ndaruppgifter", - "service_unavailable": "Ingen tj\u00e4nst tillg\u00e4nglig" + "service_unavailable": "Ingen tj\u00e4nst tillg\u00e4nglig", + "unknown_client_mac": "Ingen klient tillg\u00e4nglig p\u00e5 den MAC-adressen" }, "step": { "user": { diff --git a/homeassistant/components/vera/translations/nl.json b/homeassistant/components/vera/translations/nl.json index ff260e9bbb0..358905bd50f 100644 --- a/homeassistant/components/vera/translations/nl.json +++ b/homeassistant/components/vera/translations/nl.json @@ -1,19 +1,28 @@ { "config": { "abort": { - "already_configured": "Er is al een controller geconfigureerd." + "already_configured": "Er is al een controller geconfigureerd.", + "cannot_connect": "Kan geen verbinding maken met controller met url {base_url}" }, "step": { "user": { "data": { + "exclude": "Vera-apparaat-ID's om uit te sluiten van Home Assistant.", + "lights": "Vera-schakelapparaat id's behandelen als lichten in Home Assistant.", "vera_controller_url": "Controller-URL" - } + }, + "description": "Geef hieronder een URL voor de Vera-controller op. Het zou er zo uit moeten zien: http://192.168.1.161:3480.", + "title": "Stel Vera controller in" } } }, "options": { "step": { "init": { + "data": { + "exclude": "Vera-apparaat-ID's om uit te sluiten van Home Assistant.", + "lights": "Vera-schakelapparaat id's behandelen als lichten in Home Assistant." + }, "title": "Vera controller opties" } } diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index e1789347bf2..33777175c3e 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -10,6 +10,18 @@ "name_exists": "Vizio apparaat met opgegeven naam al geconfigureerd." }, "step": { + "pair_tv": { + "data": { + "pin": "PIN" + }, + "title": "Voltooi het koppelingsproces" + }, + "pairing_complete": { + "title": "Koppelen voltooid" + }, + "pairing_complete_import": { + "title": "Koppelen voltooid" + }, "user": { "data": { "access_token": "Toegangstoken", @@ -17,6 +29,7 @@ "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" } } @@ -25,8 +38,11 @@ "step": { "init": { "data": { + "apps_to_include_or_exclude": "Apps om op te nemen of uit te sluiten", + "include_or_exclude": "Apps opnemen of uitsluiten?", "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" } } diff --git a/homeassistant/components/weather/translations/it.json b/homeassistant/components/weather/translations/it.json index b6559782581..171b29673cd 100644 --- a/homeassistant/components/weather/translations/it.json +++ b/homeassistant/components/weather/translations/it.json @@ -15,7 +15,7 @@ "snowy-rainy": "Nevoso, piovoso", "sunny": "Soleggiato", "windy": "Ventoso", - "windy-variant": "Preval. Ventoso" + "windy-variant": "Prevalentemente ventoso" } } } \ No newline at end of file diff --git a/homeassistant/components/wled/translations/ru.json b/homeassistant/components/wled/translations/ru.json index 21b5282eb6d..4af5a3c8c81 100644 --- a/homeassistant/components/wled/translations/ru.json +++ b/homeassistant/components/wled/translations/ru.json @@ -13,7 +13,7 @@ "data": { "host": "\u0414\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 WLED \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Home Assistant.", + "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 WLED.", "title": "WLED" }, "zeroconf_confirm": { diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json new file mode 100644 index 00000000000..0e608eaf504 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "connect_error": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hlen Sie ein Ger\u00e4t aus." + }, + "step": { + "gateway": { + "data": { + "host": "IP Adresse", + "name": "Name des Gateways", + "token": "API-Token" + }, + "description": "Sie ben\u00f6tigen das API-Token. Anweisungen finden Sie unter https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "title": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + }, + "user": { + "data": { + "gateway": "Stellen Sie eine Verbindung zu einem Xiaomi Gateway her" + }, + "description": "W\u00e4hlen Sie aus, mit welchem Ger\u00e4t Sie eine Verbindung herstellen m\u00f6chten.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/fi.json b/homeassistant/components/xiaomi_miio/translations/fi.json new file mode 100644 index 00000000000..4b7dfe9ecba --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/fi.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Laite on jo m\u00e4\u00e4ritetty" + }, + "error": { + "connect_error": "Yhteyden muodostaminen ep\u00e4onnistui. Yrit\u00e4 uudelleen", + "no_device_selected": "Ei valittuja laitteita. Ole hyv\u00e4 ja valitse yksi." + }, + "step": { + "gateway": { + "data": { + "host": "IP-osoite", + "name": "Yhdysk\u00e4yt\u00e4v\u00e4n nimi", + "token": "API-tunnus" + }, + "title": "Yhdist\u00e4 Xiaomi Gatewayhin" + }, + "user": { + "data": { + "gateway": "Yhdist\u00e4 Xiaomi Gatewayhin" + }, + "description": "Valitse laite, johon haluat muodostaa yhteyden.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json new file mode 100644 index 00000000000..a99c165ebbb --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "connect_error": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", + "no_device_selected": "Nincs kiv\u00e1lasztva eszk\u00f6z, k\u00e9rj\u00fck, v\u00e1lasszon egyet." + }, + "step": { + "gateway": { + "data": { + "host": "IP-c\u00edm" + }, + "description": "Sz\u00fcks\u00e9ge lesz az API Tokenre, tov\u00e1bbi inforaciok: https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token" + }, + "user": { + "description": "V\u00e1lassza ki, melyik k\u00e9sz\u00fcl\u00e9khez szeretne csatlakozni. " + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index 8762baa8714..7c83b1134a7 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -1,20 +1,28 @@ { "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, "error": { + "connect_error": "Verbinding mislukt, probeer het opnieuw", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" }, "step": { "gateway": { "data": { - "host": "IP-adres" + "host": "IP-adres", + "name": "Naam van de gateway", + "token": "API-token" }, + "description": "U heeft het API-token nodig, zie https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token voor instructies.", "title": "Maak verbinding met een Xiaomi Gateway" }, "user": { "data": { "gateway": "Maak verbinding met een Xiaomi Gateway" }, - "description": "Selecteer het apparaat waarmee u verbinding wilt maken" + "description": "Selecteer het apparaat waarmee u verbinding wilt maken", + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/sv.json b/homeassistant/components/xiaomi_miio/translations/sv.json new file mode 100644 index 00000000000..c8714969bef --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/sv.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten \u00e4r redan konfigurerad" + }, + "error": { + "connect_error": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "no_device_selected": "Ingen enhet har valts, v\u00e4lj en enhet." + }, + "step": { + "gateway": { + "data": { + "host": "IP-adress", + "name": "Namnet p\u00e5 Gatewayen", + "token": "API Token" + }, + "description": "Du beh\u00f6ver en API token, se https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token f\u00f6r mer instruktioner.", + "title": "Anslut till en Xiaomi Gateway" + }, + "user": { + "data": { + "gateway": "Anslut till en Xiaomi Gateway" + }, + "description": "V\u00e4lj den enhet som du vill ansluta till." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/it.json b/homeassistant/components/zwave/translations/it.json index 3ea2dc94d87..8b8ffb732fc 100644 --- a/homeassistant/components/zwave/translations/it.json +++ b/homeassistant/components/zwave/translations/it.json @@ -21,13 +21,13 @@ "state": { "_": { "dead": "Disattivo", - "initializing": "Avvio", + "initializing": "In avvio", "ready": "Pronto", "sleeping": "Dormiente" }, "query_stage": { "dead": "Disattivo", - "initializing": "Avvio" + "initializing": "In avvio" } } } \ No newline at end of file From 4e55fa6c5c50bebae504a075c32ef0b3bc7e2cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel?= Date: Thu, 30 Apr 2020 21:35:02 -0300 Subject: [PATCH 169/511] Refactor Remote class in panasonic_viera (#34911) --- .coveragerc | 1 - .../components/panasonic_viera/__init__.py | 177 +++++++++++++++++- .../components/panasonic_viera/const.py | 2 + .../panasonic_viera/media_player.py | 163 ++-------------- tests/components/panasonic_viera/test_init.py | 123 ++++++++++++ 5 files changed, 311 insertions(+), 155 deletions(-) create mode 100644 tests/components/panasonic_viera/test_init.py diff --git a/.coveragerc b/.coveragerc index e2aa4243a46..fde7cb637f2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -531,7 +531,6 @@ omit = homeassistant/components/osramlightify/light.py homeassistant/components/otp/sensor.py homeassistant/components/panasonic_bluray/media_player.py - homeassistant/components/panasonic_viera/__init__.py homeassistant/components/panasonic_viera/media_player.py homeassistant/components/pandora/media_player.py homeassistant/components/pcal9535a/* diff --git a/homeassistant/components/panasonic_viera/__init__.py b/homeassistant/components/panasonic_viera/__init__.py index 60261712f4d..ebc1c20a1fa 100644 --- a/homeassistant/components/panasonic_viera/__init__.py +++ b/homeassistant/components/panasonic_viera/__init__.py @@ -1,13 +1,29 @@ """The Panasonic Viera integration.""" import asyncio +from functools import partial +import logging +from urllib.request import URLError +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.config_entries import SOURCE_IMPORT -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.script import Script -from .const import CONF_ON_ACTION, DEFAULT_NAME, DEFAULT_PORT, DOMAIN +from .const import ( + ATTR_REMOTE, + CONF_APP_ID, + CONF_ENCRYPTION_KEY, + CONF_ON_ACTION, + DEFAULT_NAME, + DEFAULT_PORT, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { @@ -28,7 +44,7 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) -PLATFORMS = ["media_player"] +PLATFORMS = [MEDIA_PLAYER_DOMAIN] async def async_setup(hass, config): @@ -49,6 +65,27 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up Panasonic Viera from a config entry.""" + panasonic_viera_data = hass.data.setdefault(DOMAIN, {}) + + config = config_entry.data + + host = config[CONF_HOST] + port = config[CONF_PORT] + + on_action = config[CONF_ON_ACTION] + if on_action is not None: + on_action = Script(hass, on_action) + + params = {} + if CONF_APP_ID in config and CONF_ENCRYPTION_KEY in config: + params["app_id"] = config[CONF_APP_ID] + params["encryption_key"] = config[CONF_ENCRYPTION_KEY] + + remote = Remote(hass, host, port, on_action, **params) + await remote.async_create_remote_control(during_setup=True) + + panasonic_viera_data[config_entry.entry_id] = {ATTR_REMOTE: remote} + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, component) @@ -59,7 +96,7 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload a config entry.""" - return all( + unload_ok = all( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(config_entry, component) @@ -67,3 +104,135 @@ async def async_unload_entry(hass, config_entry): ] ) ) + + if unload_ok: + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok + + +class Remote: + """The Remote class. It stores the TV properties and the remote control connection itself.""" + + def __init__( + self, hass, host, port, on_action=None, app_id=None, encryption_key=None, + ): + """Initialize the Remote class.""" + self._hass = hass + + self._host = host + self._port = port + + self._on_action = on_action + + self._app_id = app_id + self._encryption_key = encryption_key + + self.state = None + self.available = False + self.volume = 0 + self.muted = False + self.playing = True + + self._control = None + + async def async_create_remote_control(self, during_setup=False): + """Create remote control.""" + control_existed = self._control is not None + try: + params = {} + if self._app_id and self._encryption_key: + params["app_id"] = self._app_id + params["encryption_key"] = self._encryption_key + + self._control = await self._hass.async_add_executor_job( + partial(RemoteControl, self._host, self._port, **params) + ) + + self.state = STATE_ON + self.available = True + except (TimeoutError, URLError, SOAPError, OSError) as err: + if control_existed or during_setup: + _LOGGER.debug("Could not establish remote connection: %s", err) + + self._control = None + self.state = STATE_OFF + self.available = self._on_action is not None + except Exception as err: # pylint: disable=broad-except + if control_existed or during_setup: + _LOGGER.exception("An unknown error occurred: %s", err) + self._control = None + self.state = STATE_OFF + self.available = self._on_action is not None + + async def async_update(self): + """Update device data.""" + if self._control is None: + await self.async_create_remote_control() + return + + await self._handle_errors(self._update) + + def _update(self): + """Retrieve the latest data.""" + self.muted = self._control.get_mute() + self.volume = self._control.get_volume() / 100 + + self.state = STATE_ON + self.available = True + + async def async_send_key(self, key): + """Send a key to the TV and handle exceptions.""" + try: + key = getattr(Keys, key) + except (AttributeError, TypeError): + key = getattr(key, "value", key) + + await self._handle_errors(self._control.send_key, key) + + async def async_turn_on(self): + """Turn on the TV.""" + if self._on_action is not None: + await self._on_action.async_run() + self.state = STATE_ON + elif self.state != STATE_ON: + await self.async_send_key(Keys.power) + self.state = STATE_ON + + async def async_turn_off(self): + """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): + """Set mute based on 'enable'.""" + await self._handle_errors(self._control.set_mute, enable) + + async def async_set_volume(self, volume): + """Set volume level, range 0..1.""" + volume = int(volume * 100) + await self._handle_errors(self._control.set_volume, volume) + + async def async_play_media(self, media_type, media_id): + """Play media.""" + _LOGGER.debug("Play media: %s (%s)", media_id, media_type) + await self._handle_errors(self._control.open_webpage, media_id) + + async def _handle_errors(self, func, *args): + """Handle errors from func, set available and reconnect if needed.""" + try: + return await self._hass.async_add_executor_job(func, *args) + except EncryptionRequired: + _LOGGER.error( + "The connection couldn't be encrypted. Please reconfigure your TV" + ) + except (TimeoutError, URLError, SOAPError, OSError): + self.state = STATE_OFF + self.available = self._on_action is not None + await self.async_create_remote_control() + except Exception as err: # pylint: disable=broad-except + _LOGGER.exception("An unknown error occurred: %s", err) + self.state = STATE_OFF + self.available = self._on_action is not None diff --git a/homeassistant/components/panasonic_viera/const.py b/homeassistant/components/panasonic_viera/const.py index 434d2d3d7c4..529de4ebe67 100644 --- a/homeassistant/components/panasonic_viera/const.py +++ b/homeassistant/components/panasonic_viera/const.py @@ -10,6 +10,8 @@ CONF_ENCRYPTION_KEY = "encryption_key" DEFAULT_NAME = "Panasonic Viera TV" DEFAULT_PORT = 55000 +ATTR_REMOTE = "remote" + ERROR_NOT_CONNECTED = "not_connected" ERROR_INVALID_PIN_CODE = "invalid_pin_code" diff --git a/homeassistant/components/panasonic_viera/media_player.py b/homeassistant/components/panasonic_viera/media_player.py index ce1d3762693..7260b519bf4 100644 --- a/homeassistant/components/panasonic_viera/media_player.py +++ b/homeassistant/components/panasonic_viera/media_player.py @@ -1,9 +1,7 @@ -"""Support for interface with a Panasonic Viera TV.""" -from functools import partial +"""Media player support for Panasonic Viera TV.""" import logging -from urllib.request import URLError -from panasonic_viera import EncryptionRequired, Keys, RemoteControl, SOAPError +from panasonic_viera import Keys from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( @@ -20,10 +18,9 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, ) -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON -from homeassistant.helpers.script import Script +from homeassistant.const import CONF_NAME -from .const import CONF_APP_ID, CONF_ENCRYPTION_KEY, CONF_ON_ACTION +from .const import ATTR_REMOTE, DOMAIN SUPPORT_VIERATV = ( SUPPORT_PAUSE @@ -47,42 +44,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities): config = config_entry.data - host = config[CONF_HOST] - port = config[CONF_PORT] + remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE] name = config[CONF_NAME] - on_action = config[CONF_ON_ACTION] - if on_action is not None: - on_action = Script(hass, on_action) - - params = {} - if CONF_APP_ID in config and CONF_ENCRYPTION_KEY in config: - params["app_id"] = config[CONF_APP_ID] - params["encryption_key"] = config[CONF_ENCRYPTION_KEY] - - remote = Remote(hass, host, port, on_action, **params) - await remote.async_create_remote_control(during_setup=True) - - tv_device = PanasonicVieraTVDevice(remote, name) - + tv_device = PanasonicVieraTVEntity(remote, name) async_add_entities([tv_device]) -class PanasonicVieraTVDevice(MediaPlayerEntity): +class PanasonicVieraTVEntity(MediaPlayerEntity): """Representation of a Panasonic Viera TV.""" - def __init__( - self, remote, name, uuid=None, - ): - """Initialize the Panasonic device.""" - # Save a reference to the imported class + def __init__(self, remote, name, uuid=None): + """Initialize the entity.""" self._remote = remote self._name = name self._uuid = uuid @property - def unique_id(self) -> str: - """Return the unique ID of this Viera TV.""" + def unique_id(self): + """Return the unique ID of the device.""" return self._uuid @property @@ -97,7 +77,7 @@ class PanasonicVieraTVDevice(MediaPlayerEntity): @property def available(self): - """Return if True the device is available.""" + """Return True if the device is available.""" return self._remote.available @property @@ -176,125 +156,8 @@ class PanasonicVieraTVDevice(MediaPlayerEntity): async def async_play_media(self, media_type, media_id, **kwargs): """Play media.""" - await self._remote.async_play_media(media_type, media_id) - - -class Remote: - """The Remote class. It stores the TV properties and the remote control connection itself.""" - - def __init__( - self, hass, host, port, on_action=None, app_id=None, encryption_key=None, - ): - """Initialize the Remote class.""" - self._hass = hass - - self._host = host - self._port = port - - self._on_action = on_action - - self._app_id = app_id - self._encryption_key = encryption_key - - self.state = None - self.available = False - self.volume = 0 - self.muted = False - self.playing = True - - self._control = None - - async def async_create_remote_control(self, during_setup=False): - """Create remote control.""" - control_existed = self._control is not None - try: - params = {} - if self._app_id and self._encryption_key: - params["app_id"] = self._app_id - params["encryption_key"] = self._encryption_key - - self._control = await self._hass.async_add_executor_job( - partial(RemoteControl, self._host, self._port, **params) - ) - - self.state = STATE_ON - self.available = True - except (TimeoutError, URLError, SOAPError, OSError) as err: - if control_existed or during_setup: - _LOGGER.error("Could not establish remote connection: %s", err) - - self._control = None - self.state = STATE_OFF - self.available = self._on_action is not None - except Exception as err: # pylint: disable=broad-except - if control_existed or during_setup: - _LOGGER.exception("An unknown error occurred: %s", err) - self._control = None - self.state = STATE_OFF - self.available = self._on_action is not None - - async def async_update(self): - """Update device data.""" - if self._control is None: - await self.async_create_remote_control() - return - - await self._handle_errors(self._update) - - async def _update(self): - """Retrieve the latest data.""" - self.muted = self._control.get_mute() - self.volume = self._control.get_volume() / 100 - - self.state = STATE_ON - self.available = True - - async def async_send_key(self, key): - """Send a key to the TV and handle exceptions.""" - await self._handle_errors(self._control.send_key, key) - - async def async_turn_on(self): - """Turn on the TV.""" - if self._on_action is not None: - await self._on_action.async_run() - self.state = STATE_ON - elif self.state != STATE_ON: - await self.async_send_key(Keys.power) - self.state = STATE_ON - - async def async_turn_off(self): - """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): - """Set mute based on 'enable'.""" - await self._handle_errors(self._control.set_mute, enable) - - async def async_set_volume(self, volume): - """Set volume level, range 0..1.""" - volume = int(volume * 100) - await self._handle_errors(self._control.set_volume, volume) - - async def async_play_media(self, media_type, media_id): - """Play media.""" - _LOGGER.debug("Play media: %s (%s)", media_id, media_type) - if media_type != MEDIA_TYPE_URL: _LOGGER.warning("Unsupported media_type: %s", media_type) return - await self._handle_errors(self._control.open_webpage, media_id) - - async def _handle_errors(self, func, *args): - """Handle errors from func, set available and reconnect if needed.""" - try: - await self._hass.async_add_executor_job(func, *args) - except EncryptionRequired: - _LOGGER.error("The connection couldn't be encrypted") - except (TimeoutError, URLError, SOAPError, OSError): - self.state = STATE_OFF - self.available = self._on_action is not None - await self.async_create_remote_control() + await self._remote.async_play_media(media_type, media_id) diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py new file mode 100644 index 00000000000..3e02ac3703a --- /dev/null +++ b/tests/components/panasonic_viera/test_init.py @@ -0,0 +1,123 @@ +"""Test the Panasonic Viera setup process.""" +from unittest.mock import Mock + +from asynctest import patch + +from homeassistant.components.panasonic_viera.const import ( + CONF_APP_ID, + CONF_ENCRYPTION_KEY, + CONF_ON_ACTION, + DEFAULT_NAME, + DEFAULT_PORT, + DOMAIN, +) +from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.setup import async_setup_component + +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", +} + + +def get_mock_remote(): + """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 + + return mock_remote + + +async def test_setup_entry_encrypted(hass): + """Test setup with encrypted config entry.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, + unique_id=MOCK_CONFIG_DATA[CONF_HOST], + data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, + ) + + 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() + + state = hass.states.get("media_player.panasonic_viera_tv") + + assert state + assert state.name == DEFAULT_NAME + + +async def test_setup_entry_unencrypted(hass): + """Test setup with unencrypted config entry.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, unique_id=MOCK_CONFIG_DATA[CONF_HOST], data=MOCK_CONFIG_DATA, + ) + + 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() + + state = hass.states.get("media_player.panasonic_viera_tv") + + assert state + assert state.name == DEFAULT_NAME + + +async def test_setup_config_flow_initiated(hass): + """Test if config flow is initiated in setup.""" + assert ( + await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_HOST: "0.0.0.0"}},) + is True + ) + + assert len(hass.config_entries.flow.async_progress()) == 1 + + +async def test_setup_unload_entry(hass): + """Test if config entry is unloaded.""" + mock_entry = MockConfigEntry( + domain=DOMAIN, unique_id=MOCK_CONFIG_DATA[CONF_HOST], data=MOCK_CONFIG_DATA + ) + + 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_unload(mock_entry.entry_id) + + assert mock_entry.state == ENTRY_STATE_NOT_LOADED + + state = hass.states.get("media_player.panasonic_viera_tv") + + assert state is None From 208fa84a279d99b3a30a0ee1f899e6898a4a0a05 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 19:40:31 -0500 Subject: [PATCH 170/511] Update excess powerwall logging to be debug (#34994) --- homeassistant/components/powerwall/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index 336c3ac0bff..fa9c81533e7 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -124,9 +124,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_update_data(): """Fetch data from API endpoint.""" # Check if we had an error before - _LOGGER.info("Checking if update failed") + _LOGGER.debug("Checking if update failed") if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: - _LOGGER.info("Updating data") + _LOGGER.debug("Updating data") try: return await hass.async_add_executor_job( _fetch_powerwall_data, power_wall From cfc0edff6b35abe0836776d162a65abe89be2ceb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 19:41:24 -0500 Subject: [PATCH 171/511] Log the rachio webhook url (#34992) --- homeassistant/components/rachio/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/rachio/__init__.py b/homeassistant/components/rachio/__init__.py index a5c9f5ab0a9..b84ccb8fa5d 100644 --- a/homeassistant/components/rachio/__init__.py +++ b/homeassistant/components/rachio/__init__.py @@ -123,7 +123,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if not person.controllers: _LOGGER.error("No Rachio devices found in account %s", person.username) return False - _LOGGER.info("%d Rachio device(s) found", len(person.controllers)) + _LOGGER.info( + "%d Rachio device(s) found; The url %s must be accessible from the internet in order to receive updates", + len(person.controllers), + webhook_url, + ) # Enable component hass.data[DOMAIN][entry.entry_id] = person From a2efc079f128c15ca690886d1fa7bcd81e0dbe56 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 20:25:59 -0500 Subject: [PATCH 172/511] Add battery sensors to hunterdouglas_powerview (#34917) --- .coveragerc | 1 + .../hunterdouglas_powerview/__init__.py | 2 +- .../hunterdouglas_powerview/const.py | 2 + .../hunterdouglas_powerview/cover.py | 37 +------- .../hunterdouglas_powerview/entity.py | 33 +++++++ .../hunterdouglas_powerview/sensor.py | 86 +++++++++++++++++++ 6 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 homeassistant/components/hunterdouglas_powerview/sensor.py diff --git a/.coveragerc b/.coveragerc index fde7cb637f2..59ef37cd932 100644 --- a/.coveragerc +++ b/.coveragerc @@ -313,6 +313,7 @@ omit = homeassistant/components/hue/light.py homeassistant/components/hunterdouglas_powerview/__init__.py homeassistant/components/hunterdouglas_powerview/scene.py + homeassistant/components/hunterdouglas_powerview/sensor.py homeassistant/components/hunterdouglas_powerview/cover.py homeassistant/components/hunterdouglas_powerview/entity.py homeassistant/components/hydrawise/* diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 44ebf25a4f4..89dc610a6fc 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -69,7 +69,7 @@ CONFIG_SCHEMA = vol.Schema( ) -PLATFORMS = ["cover", "scene"] +PLATFORMS = ["cover", "scene", "sensor"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index 9979cfb186c..17ff3821a7a 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -51,6 +51,8 @@ ROOM_NAME_UNICODE = "name_unicode" ROOM_ID = "id" SHADE_RESPONSE = "shade" +SHADE_BATTERY_LEVEL = "batteryStrength" +SHADE_BATTERY_LEVEL_MAX = 200 STATE_ATTRIBUTE_ROOM_NAME = "roomName" diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 45fd798238f..8364b3273ca 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -5,7 +5,6 @@ import logging from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA from aiopvapi.resources.shade import ( ATTR_POSKIND1, - ATTR_TYPE, MAX_POSITION, MIN_POSITION, factory as PvShade, @@ -28,13 +27,7 @@ from .const import ( COORDINATOR, DEVICE_INFO, DEVICE_MODEL, - DEVICE_SERIAL_NUMBER, DOMAIN, - FIRMWARE_BUILD, - FIRMWARE_IN_SHADE, - FIRMWARE_REVISION, - FIRMWARE_SUB_REVISION, - MANUFACTURER, PV_API, PV_ROOM_DATA, PV_SHADE_DATA, @@ -43,7 +36,7 @@ from .const import ( SHADE_RESPONSE, STATE_ATTRIBUTE_ROOM_NAME, ) -from .entity import HDEntity +from .entity import ShadeEntity _LOGGER = logging.getLogger(__name__) @@ -93,21 +86,19 @@ def hass_position_to_hd(hass_positon): return int(hass_positon / 100 * MAX_POSITION) -class PowerViewShade(HDEntity, CoverEntity): +class PowerViewShade(ShadeEntity, CoverEntity): """Representation of a powerview shade.""" def __init__(self, shade, name, room_data, coordinator, device_info): """Initialize the shade.""" room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) - super().__init__(coordinator, device_info, shade.id) + super().__init__(coordinator, device_info, shade, name) self._shade = shade self._device_info = device_info self._is_opening = False self._is_closing = False - self._room_name = None self._last_action_timestamp = 0 self._scheduled_transition_update = None - self._name = name self._room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") self._current_cover_position = MIN_POSITION self._coordinator = coordinator @@ -153,7 +144,7 @@ class PowerViewShade(HDEntity, CoverEntity): @property def name(self): """Return the name of the shade.""" - return self._name + return self._shade_name async def async_close_cover(self, **kwargs): """Close the cover.""" @@ -268,26 +259,6 @@ class PowerViewShade(HDEntity, CoverEntity): self._async_update_current_cover_position() self.async_write_ha_state() - @property - def device_info(self): - """Return the device_info of the device.""" - firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] - sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" - model = self._shade.raw_data[ATTR_TYPE] - for shade in self._shade.shade_types: - if shade.shade_type == model: - model = shade.description - break - - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "model": str(model), - "sw_version": sw_version, - "manufacturer": MANUFACTURER, - "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), - } - async def async_added_to_hass(self): """When entity is added to hass.""" self._async_update_current_cover_position() diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index 03d20e027b8..3c98eeaf615 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -1,5 +1,7 @@ """The nexia integration base entity.""" +from aiopvapi.resources.shade import ATTR_TYPE + import homeassistant.helpers.device_registry as dr from homeassistant.helpers.entity import Entity @@ -11,6 +13,7 @@ from .const import ( DEVICE_SERIAL_NUMBER, DOMAIN, FIRMWARE_BUILD, + FIRMWARE_IN_SHADE, FIRMWARE_REVISION, FIRMWARE_SUB_REVISION, MANUFACTURER, @@ -57,3 +60,33 @@ class HDEntity(Entity): "sw_version": sw_version, "manufacturer": MANUFACTURER, } + + +class ShadeEntity(HDEntity): + """Base class for hunter douglas shade entities.""" + + def __init__(self, coordinator, device_info, shade, shade_name): + """Initialize the shade.""" + super().__init__(coordinator, device_info, shade.id) + self._shade_name = shade_name + self._shade = shade + + @property + def device_info(self): + """Return the device_info of the device.""" + firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] + sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" + model = self._shade.raw_data[ATTR_TYPE] + for shade in self._shade.shade_types: + if shade.shade_type == model: + model = shade.description + break + + return { + "identifiers": {(DOMAIN, self._shade.id)}, + "name": self._shade_name, + "model": str(model), + "sw_version": sw_version, + "manufacturer": MANUFACTURER, + "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), + } diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py new file mode 100644 index 00000000000..794fdac3eac --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -0,0 +1,86 @@ +"""Support for hunterdouglass_powerview sensors.""" +import logging + +from aiopvapi.resources.shade import factory as PvShade + +from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE +from homeassistant.core import callback + +from .const import ( + COORDINATOR, + DEVICE_INFO, + DOMAIN, + PV_API, + PV_SHADE_DATA, + SHADE_BATTERY_LEVEL, + SHADE_BATTERY_LEVEL_MAX, +) +from .entity import ShadeEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the hunter douglas shades sensors.""" + + pv_data = hass.data[DOMAIN][entry.entry_id] + shade_data = pv_data[PV_SHADE_DATA] + pv_request = pv_data[PV_API] + coordinator = pv_data[COORDINATOR] + device_info = pv_data[DEVICE_INFO] + + entities = [] + for raw_shade in shade_data.values(): + shade = PvShade(raw_shade, pv_request) + if SHADE_BATTERY_LEVEL not in shade.raw_data: + continue + name_before_refresh = shade.name + entities.append( + PowerViewShadeBatterySensor( + coordinator, device_info, shade, name_before_refresh + ) + ) + async_add_entities(entities) + + +class PowerViewShadeBatterySensor(ShadeEntity): + """Representation of an shade battery charge sensor.""" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return UNIT_PERCENTAGE + + @property + def name(self): + """Name of the shade battery.""" + return f"{self._shade_name} Battery" + + @property + def device_class(self): + """Shade battery Class.""" + return DEVICE_CLASS_BATTERY + + @property + def unique_id(self): + """Shade battery Uniqueid.""" + return f"{self._unique_id}_charge" + + @property + def state(self): + """Get the current value in percentage.""" + return round( + self._shade.raw_data[SHADE_BATTERY_LEVEL] / SHADE_BATTERY_LEVEL_MAX * 100 + ) + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self.async_on_remove( + self._coordinator.async_add_listener(self._async_update_shade_from_group) + ) + + @callback + def _async_update_shade_from_group(self): + """Update with new data from the coordinator.""" + self._shade.raw_data = self._coordinator.data[self._shade.id] + self.async_write_ha_state() From 5699cb8a09d1f2e16ab21412c8ab46ac72a10769 Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Thu, 30 Apr 2020 18:35:32 -0700 Subject: [PATCH 173/511] Add allow extra to totalconnect config schema (#34993) --- homeassistant/components/totalconnect/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/totalconnect/__init__.py b/homeassistant/components/totalconnect/__init__.py index fce67f71b24..25b1141bd9b 100644 --- a/homeassistant/components/totalconnect/__init__.py +++ b/homeassistant/components/totalconnect/__init__.py @@ -24,7 +24,8 @@ CONFIG_SCHEMA = vol.Schema( vol.Required(CONF_PASSWORD): cv.string, } ) - } + }, + extra=vol.ALLOW_EXTRA, ) From 793592b2b841de658c75147b65d44f1e13546867 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 30 Apr 2020 23:05:06 -0500 Subject: [PATCH 174/511] Config flow for homekit (#34560) * Config flow for homekit Allows multiple homekit bridges to run HAP-python state is now stored at .storage/homekit.{entry_id}.state aids is now stored at .storage/homekit.{entry_id}.aids Overcomes 150 device limit by supporting multiple bridges. Name and port are now automatically allocated to avoid conflicts which was one of the main reasons pairing failed. YAML configuration remains available in order to offer entity specific configuration. Entries created by config flow can add and remove included domains and entities without having to restart * Fix services as there are multiple now * migrate in executor * drop title from strings * Update homeassistant/components/homekit/strings.json Co-authored-by: Paulus Schoutsen * Make auto_start advanced mode only, add coverage * put back title * more references * delete port since manual config is no longer needed Co-authored-by: Paulus Schoutsen --- homeassistant/components/homekit/__init__.py | 488 ++++++++++-------- .../components/homekit/accessories.py | 153 +++++- .../components/homekit/aidmanager.py | 12 +- .../components/homekit/config_flow.py | 301 +++++++++++ homeassistant/components/homekit/const.py | 16 +- .../components/homekit/manifest.json | 3 +- homeassistant/components/homekit/strings.json | 54 ++ .../components/homekit/translations/en.json | 54 ++ .../components/homekit/type_covers.py | 3 +- homeassistant/components/homekit/type_fans.py | 3 +- .../components/homekit/type_lights.py | 3 +- .../components/homekit/type_locks.py | 3 +- .../components/homekit/type_media_players.py | 3 +- .../homekit/type_security_systems.py | 3 +- .../components/homekit/type_sensors.py | 3 +- .../components/homekit/type_switches.py | 3 +- .../components/homekit/type_thermostats.py | 3 +- homeassistant/components/homekit/util.py | 107 +++- homeassistant/generated/config_flows.py | 1 + homeassistant/helpers/entityfilter.py | 30 +- tests/components/homekit/test_accessories.py | 8 +- tests/components/homekit/test_aidmanager.py | 15 +- tests/components/homekit/test_config_flow.py | 259 ++++++++++ .../homekit/test_get_accessories.py | 27 +- tests/components/homekit/test_homekit.py | 441 ++++++++++++++-- .../homekit/test_type_media_players.py | 1 + .../homekit/test_type_security_systems.py | 1 + tests/components/homekit/test_util.py | 44 +- tests/components/homekit/util.py | 34 ++ 29 files changed, 1754 insertions(+), 322 deletions(-) create mode 100644 homeassistant/components/homekit/config_flow.py create mode 100644 homeassistant/components/homekit/strings.json create mode 100644 homeassistant/components/homekit/translations/en.json create mode 100644 tests/components/homekit/test_config_flow.py create mode 100644 tests/components/homekit/util.py diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index c77ec36ccf3..184fce2309b 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -1,4 +1,5 @@ """Support for Apple HomeKit.""" +import asyncio import ipaddress import logging @@ -6,41 +7,36 @@ from aiohttp import web import voluptuous as vol from zeroconf import InterfaceChoice -from homeassistant.components import cover, vacuum from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING -from homeassistant.components.cover import DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE from homeassistant.components.http import HomeAssistantView -from homeassistant.components.media_player import DEVICE_CLASS_TV +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, - ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SERVICE, - ATTR_SUPPORTED_FEATURES, - ATTR_UNIT_OF_MEASUREMENT, CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, - CONF_TYPE, DEVICE_CLASS_BATTERY, - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_ILLUMINANCE, - DEVICE_CLASS_TEMPERATURE, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, - TEMP_CELSIUS, - TEMP_FAHRENHEIT, - UNIT_PERCENTAGE, ) -from homeassistant.core import callback -from homeassistant.exceptions import Unauthorized -from homeassistant.helpers import entity_registry +from homeassistant.core import CoreState, HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady, Unauthorized +from homeassistant.helpers import device_registry, entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entityfilter import FILTER_SCHEMA +from homeassistant.helpers.entityfilter import ( + BASE_FILTER_SCHEMA, + CONF_EXCLUDE_DOMAINS, + CONF_EXCLUDE_ENTITIES, + CONF_INCLUDE_DOMAINS, + CONF_INCLUDE_ENTITIES, + convert_filter, +) from homeassistant.util import get_local_ip -from homeassistant.util.decorator import Registry +from .accessories import get_accessory from .aidmanager import AccessoryAidStorage from .const import ( AID_STORAGE, @@ -50,43 +46,41 @@ from .const import ( CONF_ADVERTISE_IP, CONF_AUTO_START, CONF_ENTITY_CONFIG, - CONF_FEATURE_LIST, + CONF_ENTRY_INDEX, CONF_FILTER, CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, CONF_SAFE_MODE, CONF_ZEROCONF_DEFAULT_INTERFACE, + CONFIG_OPTIONS, DEFAULT_AUTO_START, DEFAULT_PORT, DEFAULT_SAFE_MODE, DEFAULT_ZEROCONF_DEFAULT_INTERFACE, - DEVICE_CLASS_CO, - DEVICE_CLASS_CO2, - DEVICE_CLASS_PM25, DOMAIN, EVENT_HOMEKIT_CHANGED, - HOMEKIT_FILE, + HOMEKIT, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, + MANUFACTURER, SERVICE_HOMEKIT_RESET_ACCESSORY, SERVICE_HOMEKIT_START, - TYPE_FAUCET, - TYPE_OUTLET, - TYPE_SHOWER, - TYPE_SPRINKLER, - TYPE_SWITCH, - TYPE_VALVE, + SHUTDOWN_TIMEOUT, + UNDO_UPDATE_LISTENER, ) from .util import ( + dismiss_setup_message, + get_persist_fullpath_for_entry_id, + migrate_filesystem_state_data_for_primary_imported_entry_id, + port_is_available, + remove_state_files_for_entry_id, show_setup_message, validate_entity_config, - validate_media_player_features, ) _LOGGER = logging.getLogger(__name__) MAX_DEVICES = 150 -TYPES = Registry() # #### Driver Status #### STATUS_READY = 0 @@ -94,66 +88,139 @@ STATUS_RUNNING = 1 STATUS_STOPPED = 2 STATUS_WAIT = 3 -SWITCH_TYPES = { - TYPE_FAUCET: "Valve", - TYPE_OUTLET: "Outlet", - TYPE_SHOWER: "Valve", - TYPE_SPRINKLER: "Valve", - TYPE_SWITCH: "Switch", - TYPE_VALVE: "Valve", -} -CONFIG_SCHEMA = vol.Schema( +def _has_all_unique_names_and_ports(bridges): + """Validate that each homekit bridge configured has a unique name.""" + names = [bridge[CONF_NAME] for bridge in bridges] + ports = [bridge[CONF_PORT] for bridge in bridges] + vol.Schema(vol.Unique())(names) + vol.Schema(vol.Unique())(ports) + return bridges + + +BRIDGE_SCHEMA = vol.Schema( { - DOMAIN: vol.All( - { - vol.Optional(CONF_NAME, default=BRIDGE_NAME): vol.All( - cv.string, vol.Length(min=3, max=25) - ), - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string), - vol.Optional(CONF_ADVERTISE_IP): vol.All( - ipaddress.ip_address, cv.string - ), - vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean, - vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean, - vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, - vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, - vol.Optional( - CONF_ZEROCONF_DEFAULT_INTERFACE, - default=DEFAULT_ZEROCONF_DEFAULT_INTERFACE, - ): cv.boolean, - } - ) + vol.Optional(CONF_NAME, default=BRIDGE_NAME): vol.All( + cv.string, vol.Length(min=3, max=25) + ), + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string), + vol.Optional(CONF_ADVERTISE_IP): vol.All(ipaddress.ip_address, cv.string), + vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): cv.boolean, + vol.Optional(CONF_SAFE_MODE, default=DEFAULT_SAFE_MODE): cv.boolean, + vol.Optional(CONF_FILTER, default={}): BASE_FILTER_SCHEMA, + vol.Optional(CONF_ENTITY_CONFIG, default={}): validate_entity_config, + vol.Optional( + CONF_ZEROCONF_DEFAULT_INTERFACE, default=DEFAULT_ZEROCONF_DEFAULT_INTERFACE, + ): cv.boolean, }, extra=vol.ALLOW_EXTRA, ) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.All(cv.ensure_list, [BRIDGE_SCHEMA], _has_all_unique_names_and_ports)}, + extra=vol.ALLOW_EXTRA, +) + + RESET_ACCESSORY_SERVICE_SCHEMA = vol.Schema( {vol.Required(ATTR_ENTITY_ID): cv.entity_ids} ) -async def async_setup(hass, config): - """Set up the HomeKit component.""" - _LOGGER.debug("Begin setup HomeKit") +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the HomeKit from yaml.""" - aid_storage = hass.data[AID_STORAGE] = AccessoryAidStorage(hass) - await aid_storage.async_initialize() + hass.data.setdefault(DOMAIN, {}) - hass.http.register_view(HomeKitPairingQRView) + _async_register_events_and_services(hass) + + if DOMAIN not in config: + return True + + current_entries = hass.config_entries.async_entries(DOMAIN) + + entries_by_name = {entry.data[CONF_NAME]: entry for entry in current_entries} + + for index, conf in enumerate(config[DOMAIN]): + bridge_name = conf[CONF_NAME] + + if ( + bridge_name in entries_by_name + and entries_by_name[bridge_name].source == SOURCE_IMPORT + ): + entry = entries_by_name[bridge_name] + # If they alter the yaml config we import the changes + # since there currently is no practical way to support + # all the options in the UI at this time. + data = conf.copy() + options = {} + for key in CONFIG_OPTIONS: + options[key] = data[key] + del data[key] + + hass.config_entries.async_update_entry(entry, data=data, options=options) + continue + + conf[CONF_ENTRY_INDEX] = index + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf, + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up HomeKit from a config entry.""" + _async_import_options_from_data_if_missing(hass, entry) + + conf = entry.data + options = entry.options - conf = config[DOMAIN] name = conf[CONF_NAME] port = conf[CONF_PORT] + _LOGGER.debug("Begin setup HomeKit for %s", name) + + # If the previous instance hasn't cleaned up yet + # we need to wait a bit + if not await hass.async_add_executor_job(port_is_available, port): + raise ConfigEntryNotReady + + if CONF_ENTRY_INDEX in conf and conf[CONF_ENTRY_INDEX] == 0: + _LOGGER.debug("Migrating legacy HomeKit data for %s", name) + hass.async_add_executor_job( + migrate_filesystem_state_data_for_primary_imported_entry_id, + hass, + entry.entry_id, + ) + + aid_storage = AccessoryAidStorage(hass, entry.entry_id) + + await aid_storage.async_initialize() + # These are yaml only ip_address = conf.get(CONF_IP_ADDRESS) advertise_ip = conf.get(CONF_ADVERTISE_IP) - auto_start = conf[CONF_AUTO_START] - safe_mode = conf[CONF_SAFE_MODE] - entity_filter = conf[CONF_FILTER] - entity_config = conf[CONF_ENTITY_CONFIG] + entity_config = conf.get(CONF_ENTITY_CONFIG, {}) + + auto_start = options.get(CONF_AUTO_START, DEFAULT_AUTO_START) + safe_mode = options.get(CONF_SAFE_MODE, DEFAULT_SAFE_MODE) + entity_filter = convert_filter( + options.get( + CONF_FILTER, + { + CONF_INCLUDE_DOMAINS: [], + CONF_EXCLUDE_DOMAINS: [], + CONF_INCLUDE_ENTITIES: [], + CONF_EXCLUDE_ENTITIES: [], + }, + ) + ) interface_choice = ( - InterfaceChoice.Default if conf.get(CONF_ZEROCONF_DEFAULT_INTERFACE) else None + InterfaceChoice.Default + if options.get(CONF_ZEROCONF_DEFAULT_INTERFACE) + else None ) homekit = HomeKit( @@ -166,20 +233,100 @@ async def async_setup(hass, config): safe_mode, advertise_ip, interface_choice, + entry.entry_id, ) await hass.async_add_executor_job(homekit.setup) + 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, + } + + if hass.state == CoreState.running: + await homekit.async_start() + elif auto_start: + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, homekit.async_start) + + return True + + +async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Handle options update.""" + if entry.source == SOURCE_IMPORT: + return + await hass.config_entries.async_reload(entry.entry_id) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + + dismiss_setup_message(hass, entry.entry_id) + + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() + + homekit = hass.data[DOMAIN][entry.entry_id][HOMEKIT] + + if homekit.status == STATUS_RUNNING: + await homekit.async_stop() + + for _ in range(0, SHUTDOWN_TIMEOUT): + if not await hass.async_add_executor_job( + port_is_available, entry.data[CONF_PORT] + ): + _LOGGER.info("Waiting for the HomeKit server to shutdown.") + await asyncio.sleep(1) + + hass.data[DOMAIN].pop(entry.entry_id) + + return True + + +async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry): + """Remove a config entry.""" + return await hass.async_add_executor_job( + remove_state_files_for_entry_id, hass, entry.entry_id + ) + + +@callback +def _async_import_options_from_data_if_missing(hass: HomeAssistant, entry: ConfigEntry): + options = dict(entry.options) + data = dict(entry.data) + modified = False + for importable_option in CONFIG_OPTIONS: + if importable_option not in entry.options and importable_option in entry.data: + options[importable_option] = entry.data[importable_option] + del data[importable_option] + modified = True + + if modified: + hass.config_entries.async_update_entry(entry, data=data, options=options) + + +@callback +def _async_register_events_and_services(hass: HomeAssistant): + """Register events and services for HomeKit.""" + + hass.http.register_view(HomeKitPairingQRView) + def handle_homekit_reset_accessory(service): """Handle start HomeKit service call.""" - if homekit.status != STATUS_RUNNING: - _LOGGER.warning( - "HomeKit is not running. Either it is waiting to be " - "started or has been stopped." - ) - return + for entry_id in hass.data[DOMAIN]: + if HOMEKIT not in hass.data[DOMAIN][entry_id]: + continue + homekit = hass.data[DOMAIN][entry_id][HOMEKIT] + if homekit.status != STATUS_RUNNING: + _LOGGER.warning( + "HomeKit is not running. Either it is waiting to be " + "started or has been stopped." + ) + continue - entity_ids = service.data.get("entity_id") - homekit.reset_accessories(entity_ids) + entity_ids = service.data.get("entity_id") + homekit.reset_accessories(entity_ids) hass.services.async_register( DOMAIN, @@ -208,124 +355,24 @@ async def async_setup(hass, config): DOMAIN, EVENT_HOMEKIT_CHANGED, async_describe_logbook_event ) - if auto_start: - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, homekit.async_start) - return True - async def async_handle_homekit_service_start(service): """Handle start HomeKit service call.""" - if homekit.status != STATUS_READY: - _LOGGER.warning( - "HomeKit is not ready. Either it is already running or has " - "been stopped." - ) - return - await homekit.async_start() + for entry_id in hass.data[DOMAIN]: + if HOMEKIT not in hass.data[DOMAIN][entry_id]: + continue + homekit = hass.data[DOMAIN][entry_id][HOMEKIT] + if homekit.status != STATUS_READY: + _LOGGER.warning( + "HomeKit is not ready. Either it is already running or has " + "been stopped." + ) + continue + await homekit.async_start() hass.services.async_register( DOMAIN, SERVICE_HOMEKIT_START, async_handle_homekit_service_start ) - return True - - -def get_accessory(hass, driver, state, aid, config): - """Take state and return an accessory object if supported.""" - if not aid: - _LOGGER.warning( - 'The entity "%s" is not supported, since it ' - "generates an invalid aid, please change it.", - state.entity_id, - ) - return None - - a_type = None - name = config.get(CONF_NAME, state.name) - - if state.domain == "alarm_control_panel": - a_type = "SecuritySystem" - - elif state.domain in ("binary_sensor", "device_tracker", "person"): - a_type = "BinarySensor" - - elif state.domain == "climate": - a_type = "Thermostat" - - 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 - ): - a_type = "GarageDoorOpener" - elif features & cover.SUPPORT_SET_POSITION: - a_type = "WindowCovering" - elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): - a_type = "WindowCoveringBasic" - - elif state.domain == "fan": - a_type = "Fan" - - elif state.domain == "light": - a_type = "Light" - - elif state.domain == "lock": - a_type = "Lock" - - elif state.domain == "media_player": - device_class = state.attributes.get(ATTR_DEVICE_CLASS) - feature_list = config.get(CONF_FEATURE_LIST) - - if device_class == DEVICE_CLASS_TV: - a_type = "TelevisionMediaPlayer" - else: - if feature_list and validate_media_player_features(state, feature_list): - a_type = "MediaPlayer" - - elif state.domain == "sensor": - device_class = state.attributes.get(ATTR_DEVICE_CLASS) - unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - - if device_class == DEVICE_CLASS_TEMPERATURE or unit in ( - TEMP_CELSIUS, - TEMP_FAHRENHEIT, - ): - a_type = "TemperatureSensor" - elif device_class == DEVICE_CLASS_HUMIDITY and unit == UNIT_PERCENTAGE: - a_type = "HumiditySensor" - elif device_class == DEVICE_CLASS_PM25 or DEVICE_CLASS_PM25 in state.entity_id: - 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: - a_type = "CarbonDioxideSensor" - elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", "lx"): - a_type = "LightSensor" - - elif state.domain == "switch": - switch_type = config.get(CONF_TYPE, TYPE_SWITCH) - a_type = SWITCH_TYPES[switch_type] - - elif state.domain == "vacuum": - features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) - if features & (vacuum.SUPPORT_START | vacuum.SUPPORT_RETURN_HOME): - a_type = "DockVacuum" - else: - a_type = "Switch" - - elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): - a_type = "Switch" - - elif state.domain == "water_heater": - a_type = "WaterHeater" - - if a_type is None: - return None - - _LOGGER.debug('Add "%s" as "%s"', state.entity_id, a_type) - return TYPES[a_type](hass, driver, name, state.entity_id, aid, config) - class HomeKit: """Class to handle all actions between HomeKit and Home Assistant.""" @@ -341,6 +388,7 @@ class HomeKit: safe_mode, advertise_ip=None, interface_choice=None, + entry_id=None, ): """Initialize a HomeKit object.""" self.hass = hass @@ -352,6 +400,7 @@ class HomeKit: self._safe_mode = safe_mode self._advertise_ip = advertise_ip self._interface_choice = interface_choice + self._entry_id = entry_id self.status = STATUS_READY self.bridge = None @@ -363,25 +412,26 @@ class HomeKit: from .accessories import HomeBridge, HomeDriver self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop) - ip_addr = self._ip_address or get_local_ip() - path = self.hass.config.path(HOMEKIT_FILE) + persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id) self.driver = HomeDriver( self.hass, + self._entry_id, + self._name, address=ip_addr, port=self._port, - persist_file=path, + persist_file=persist_file, advertised_address=self._advertise_ip, interface_choice=self._interface_choice, ) self.bridge = HomeBridge(self.hass, self.driver, self._name) if self._safe_mode: - _LOGGER.debug("Safe_mode selected") + _LOGGER.debug("Safe_mode selected for %s", self._name) self.driver.safe_mode = True def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" - aid_storage = self.hass.data[AID_STORAGE] + 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) @@ -412,9 +462,9 @@ class HomeKit: ) return - aid = self.hass.data[AID_STORAGE].get_or_allocate_aid_for_entity_id( - state.entity_id - ) + aid = self.hass.data[DOMAIN][self._entry_id][ + 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 @@ -437,6 +487,7 @@ class HomeKit: async def async_start(self, *args): """Start the accessory driver.""" + if self.status != STATUS_READY: return self.status = STATUS_WAIT @@ -459,6 +510,20 @@ class HomeKit: bridged_states.append(state) await self.hass.async_add_executor_job(self._start, bridged_states) + await self._async_register_bridge() + + async def _async_register_bridge(self): + """Register the bridge as a device so homekit_controller and exclude it from discovery.""" + registry = await device_registry.async_get_registry(self.hass) + registry.async_get_or_create( + config_entry_id=self._entry_id, + connections={ + (device_registry.CONNECTION_NETWORK_MAC, self.driver.state.mac) + }, + manufacturer=MANUFACTURER, + name=self._name, + model="Home Assistant HomeKit Bridge", + ) def _start(self, bridged_states): from . import ( # noqa: F401 pylint: disable=unused-import, import-outside-toplevel @@ -480,10 +545,14 @@ class HomeKit: if not self.driver.state.paired: show_setup_message( - self.hass, self.driver.state.pincode, self.bridge.xhm_uri() + self.hass, + self._entry_id, + self._name, + self.driver.state.pincode, + self.bridge.xhm_uri(), ) - _LOGGER.debug("Driver start") + _LOGGER.debug("Driver start for %s", self._name) self.hass.add_job(self.driver.start) self.status = STATUS_RUNNING @@ -492,8 +561,7 @@ class HomeKit: if self.status != STATUS_RUNNING: return self.status = STATUS_STOPPED - - _LOGGER.debug("Driver stop") + _LOGGER.debug("Driver stop for %s", self._name) self.hass.add_job(self.driver.stop) @callback @@ -539,9 +607,17 @@ class HomeKitPairingQRView(HomeAssistantView): # pylint: disable=no-self-use async def get(self, request): """Retrieve the pairing QRCode image.""" - if request.query_string != request.app["hass"].data[HOMEKIT_PAIRING_QR_SECRET]: + if not request.query_string: + raise Unauthorized() + entry_id, secret = request.query_string.split("-") + + if ( + entry_id not in request.app["hass"].data[DOMAIN] + or secret + != request.app["hass"].data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR_SECRET] + ): raise Unauthorized() return web.Response( - body=request.app["hass"].data[HOMEKIT_PAIRING_QR], + body=request.app["hass"].data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR], content_type="image/svg+xml", ) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index c3b42f0b6dc..ddafbd8fa66 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -8,12 +8,26 @@ from pyhap.accessory import Accessory, Bridge from pyhap.accessory_driver import AccessoryDriver from pyhap.const import CATEGORY_OTHER +from homeassistant.components import cover, vacuum +from homeassistant.components.cover import DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE +from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.const import ( ATTR_BATTERY_CHARGING, ATTR_BATTERY_LEVEL, + ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SERVICE, + ATTR_SUPPORTED_FEATURES, + ATTR_UNIT_OF_MEASUREMENT, + CONF_NAME, + CONF_TYPE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_TEMPERATURE, STATE_ON, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + UNIT_PERCENTAGE, __version__, ) from homeassistant.core import callback as ha_callback, split_entity_id @@ -22,6 +36,7 @@ from homeassistant.helpers.event import ( track_point_in_utc_time, ) from homeassistant.util import dt as dt_util +from homeassistant.util.decorator import Registry from .const import ( ATTR_DISPLAY_NAME, @@ -31,21 +46,45 @@ from .const import ( CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, + CONF_FEATURE_LIST, CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEBOUNCE_TIMEOUT, DEFAULT_LOW_BATTERY_THRESHOLD, + DEVICE_CLASS_CO, + DEVICE_CLASS_CO2, + DEVICE_CLASS_PM25, EVENT_HOMEKIT_CHANGED, HK_CHARGING, HK_NOT_CHARGABLE, HK_NOT_CHARGING, MANUFACTURER, SERV_BATTERY_SERVICE, + TYPE_FAUCET, + TYPE_OUTLET, + TYPE_SHOWER, + TYPE_SPRINKLER, + TYPE_SWITCH, + TYPE_VALVE, +) +from .util import ( + convert_to_float, + dismiss_setup_message, + show_setup_message, + validate_media_player_features, ) -from .util import convert_to_float, dismiss_setup_message, show_setup_message _LOGGER = logging.getLogger(__name__) +SWITCH_TYPES = { + TYPE_FAUCET: "Valve", + TYPE_OUTLET: "Outlet", + TYPE_SHOWER: "Valve", + TYPE_SPRINKLER: "Valve", + TYPE_SWITCH: "Switch", + TYPE_VALVE: "Valve", +} +TYPES = Registry() def debounce(func): @@ -79,6 +118,104 @@ def debounce(func): return wrapper +def get_accessory(hass, driver, state, aid, config): + """Take state and return an accessory object if supported.""" + if not aid: + _LOGGER.warning( + 'The entity "%s" is not supported, since it ' + "generates an invalid aid, please change it.", + state.entity_id, + ) + return None + + a_type = None + name = config.get(CONF_NAME, state.name) + + if state.domain == "alarm_control_panel": + a_type = "SecuritySystem" + + elif state.domain in ("binary_sensor", "device_tracker", "person"): + a_type = "BinarySensor" + + elif state.domain == "climate": + a_type = "Thermostat" + + 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 + ): + a_type = "GarageDoorOpener" + elif features & cover.SUPPORT_SET_POSITION: + a_type = "WindowCovering" + elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): + a_type = "WindowCoveringBasic" + + elif state.domain == "fan": + a_type = "Fan" + + elif state.domain == "light": + a_type = "Light" + + elif state.domain == "lock": + a_type = "Lock" + + elif state.domain == "media_player": + device_class = state.attributes.get(ATTR_DEVICE_CLASS) + feature_list = config.get(CONF_FEATURE_LIST) + + if device_class == DEVICE_CLASS_TV: + a_type = "TelevisionMediaPlayer" + else: + if feature_list and validate_media_player_features(state, feature_list): + a_type = "MediaPlayer" + + elif state.domain == "sensor": + device_class = state.attributes.get(ATTR_DEVICE_CLASS) + unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + + if device_class == DEVICE_CLASS_TEMPERATURE or unit in ( + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ): + a_type = "TemperatureSensor" + elif device_class == DEVICE_CLASS_HUMIDITY and unit == UNIT_PERCENTAGE: + a_type = "HumiditySensor" + elif device_class == DEVICE_CLASS_PM25 or DEVICE_CLASS_PM25 in state.entity_id: + 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: + a_type = "CarbonDioxideSensor" + elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", "lx"): + a_type = "LightSensor" + + elif state.domain == "switch": + switch_type = config.get(CONF_TYPE, TYPE_SWITCH) + a_type = SWITCH_TYPES[switch_type] + + elif state.domain == "vacuum": + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + if features & (vacuum.SUPPORT_START | vacuum.SUPPORT_RETURN_HOME): + a_type = "DockVacuum" + else: + a_type = "Switch" + + elif state.domain in ("automation", "input_boolean", "remote", "scene", "script"): + a_type = "Switch" + + elif state.domain == "water_heater": + a_type = "WaterHeater" + + if a_type is None: + return None + + _LOGGER.debug('Add "%s" as "%s"', state.entity_id, a_type) + return TYPES[a_type](hass, driver, name, state.entity_id, aid, config) + + class HomeAccessory(Accessory): """Adapter class for Accessory.""" @@ -327,19 +464,27 @@ class HomeBridge(Bridge): class HomeDriver(AccessoryDriver): """Adapter class for AccessoryDriver.""" - def __init__(self, hass, **kwargs): + def __init__(self, hass, entry_id, bridge_name, **kwargs): """Initialize a AccessoryDriver object.""" super().__init__(**kwargs) self.hass = hass + self._entry_id = entry_id + self._bridge_name = bridge_name def pair(self, client_uuid, client_public): """Override super function to dismiss setup message if paired.""" success = super().pair(client_uuid, client_public) if success: - dismiss_setup_message(self.hass) + dismiss_setup_message(self.hass, self._entry_id) return success def unpair(self, client_uuid): """Override super function to show setup message if unpaired.""" super().unpair(client_uuid) - show_setup_message(self.hass, self.state.pincode, self.accessory.xhm_uri()) + show_setup_message( + self.hass, + self._entry_id, + self._bridge_name, + self.state.pincode, + self.accessory.xhm_uri(), + ) diff --git a/homeassistant/components/homekit/aidmanager.py b/homeassistant/components/homekit/aidmanager.py index 95181114e79..487865f22ab 100644 --- a/homeassistant/components/homekit/aidmanager.py +++ b/homeassistant/components/homekit/aidmanager.py @@ -15,13 +15,13 @@ from zlib import adler32 from fnvhash import fnv1a_32 +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.storage import Store -from .const import DOMAIN +from .util import get_aid_storage_filename_for_entry_id -AID_MANAGER_STORAGE_KEY = f"{DOMAIN}.aids" AID_MANAGER_STORAGE_VERSION = 1 AID_MANAGER_SAVE_DELAY = 2 @@ -74,13 +74,13 @@ class AccessoryAidStorage: persist over reboots. """ - def __init__(self, hass: HomeAssistant): + def __init__(self, hass: HomeAssistant, entry: ConfigEntry): """Create a new entity map store.""" self.hass = hass - self.store = Store(hass, AID_MANAGER_STORAGE_VERSION, AID_MANAGER_STORAGE_KEY) self.allocations = {} self.allocated_aids = set() - + self._entry = entry + self.store = None self._entity_registry = None async def async_initialize(self): @@ -88,6 +88,8 @@ class AccessoryAidStorage: self._entity_registry = ( await self.hass.helpers.entity_registry.async_get_registry() ) + aidstore = get_aid_storage_filename_for_entry_id(self._entry) + self.store = Store(self.hass, AID_MANAGER_STORAGE_VERSION, aidstore) raw_storage = await self.store.async_load() if not raw_storage: diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py new file mode 100644 index 00000000000..0f83b7a3c24 --- /dev/null +++ b/homeassistant/components/homekit/config_flow.py @@ -0,0 +1,301 @@ +"""Config flow for HomeKit integration.""" +import logging +import random +import string + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_NAME, CONF_PORT +from homeassistant.core import callback, split_entity_id +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entityfilter import ( + CONF_EXCLUDE_DOMAINS, + CONF_EXCLUDE_ENTITIES, + CONF_INCLUDE_DOMAINS, + CONF_INCLUDE_ENTITIES, +) + +from .const import ( + CONF_AUTO_START, + CONF_FILTER, + CONF_SAFE_MODE, + CONF_ZEROCONF_DEFAULT_INTERFACE, + DEFAULT_AUTO_START, + DEFAULT_CONFIG_FLOW_PORT, + DEFAULT_SAFE_MODE, + DEFAULT_ZEROCONF_DEFAULT_INTERFACE, + SHORT_BRIDGE_NAME, +) +from .const import DOMAIN # pylint:disable=unused-import +from .util import find_next_available_port + +_LOGGER = logging.getLogger(__name__) + +CONF_DOMAINS = "domains" +SUPPORTED_DOMAINS = [ + "alarm_control_panel", + "automation", + "binary_sensor", + "climate", + "cover", + "demo", + "device_tracker", + "fan", + "input_boolean", + "light", + "lock", + "media_player", + "person", + "remote", + "scene", + "script", + "sensor", + "switch", + "vacuum", + "water_heater", +] + +DEFAULT_DOMAINS = [ + "alarm_control_panel", + "climate", + "cover", + "light", + "lock", + "media_player", + "switch", + "vacuum", + "water_heater", +] + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for HomeKit.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self): + """Initialize config flow.""" + self.homekit_data = {} + self.entry_title = None + + async def async_step_pairing(self, user_input=None): + """Pairing instructions.""" + if user_input is not None: + return self.async_create_entry( + title=self.entry_title, data=self.homekit_data + ) + return self.async_show_form( + step_id="pairing", + description_placeholders={CONF_NAME: self.homekit_data[CONF_NAME]}, + ) + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + if user_input is not None: + port = await self._async_available_port() + name = self._async_available_name() + title = f"{name}:{port}" + self.homekit_data = user_input.copy() + self.homekit_data[CONF_NAME] = name + self.homekit_data[CONF_PORT] = port + self.homekit_data[CONF_FILTER] = { + CONF_INCLUDE_DOMAINS: user_input[CONF_INCLUDE_DOMAINS], + CONF_INCLUDE_ENTITIES: [], + CONF_EXCLUDE_DOMAINS: [], + CONF_EXCLUDE_ENTITIES: [], + } + del self.homekit_data[CONF_INCLUDE_DOMAINS] + self.entry_title = title + return await self.async_step_pairing() + + default_domains = [] if self._async_current_entries() else DEFAULT_DOMAINS + setup_schema = vol.Schema( + { + vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): bool, + vol.Required( + CONF_INCLUDE_DOMAINS, default=default_domains + ): cv.multi_select(SUPPORTED_DOMAINS), + } + ) + + return self.async_show_form( + step_id="user", data_schema=setup_schema, errors=errors + ) + + async def async_step_import(self, user_input=None): + """Handle import from yaml.""" + if not self._async_is_unique_name_port(user_input): + return self.async_abort(reason="port_name_in_use") + return self.async_create_entry( + title=f"{user_input[CONF_NAME]}:{user_input[CONF_PORT]}", data=user_input + ) + + async def _async_available_port(self): + """Return an available port the bridge.""" + return await self.hass.async_add_executor_job( + find_next_available_port, DEFAULT_CONFIG_FLOW_PORT + ) + + @callback + def _async_available_name(self): + """Return an available for the bridge.""" + current_entries = self._async_current_entries() + + # We always pick a RANDOM name to avoid Zeroconf + # name collisions. If the name has been seen before + # pairing will probably fail. + acceptable_chars = string.ascii_uppercase + string.digits + trailer = "".join(random.choices(acceptable_chars, k=4)) + all_names = {entry.data[CONF_NAME] for entry in current_entries} + suggested_name = f"{SHORT_BRIDGE_NAME} {trailer}" + while suggested_name in all_names: + trailer = "".join(random.choices(acceptable_chars, k=4)) + suggested_name = f"{SHORT_BRIDGE_NAME} {trailer}" + + return suggested_name + + @callback + def _async_is_unique_name_port(self, user_input): + """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 + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a option flow for tado.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize options flow.""" + self.config_entry = config_entry + self.homekit_options = {} + + async def async_step_yaml(self, user_input=None): + """No options for yaml managed entries.""" + if user_input is not None: + # Apparently not possible to abort an options flow + # at the moment + return self.async_create_entry(title="", data=self.config_entry.options) + + return self.async_show_form(step_id="yaml") + + async def async_step_advanced(self, user_input=None): + """Choose advanced options.""" + if user_input is not None: + self.homekit_options.update(user_input) + del self.homekit_options[CONF_INCLUDE_DOMAINS] + return self.async_create_entry(title="", data=self.homekit_options) + + schema_base = {} + + if self.show_advanced_options: + schema_base[ + vol.Optional( + CONF_AUTO_START, + default=self.homekit_options.get( + CONF_AUTO_START, DEFAULT_AUTO_START + ), + ) + ] = bool + else: + self.homekit_options[CONF_AUTO_START] = self.homekit_options.get( + CONF_AUTO_START, DEFAULT_AUTO_START + ) + + schema_base.update( + { + vol.Optional( + CONF_SAFE_MODE, + default=self.homekit_options.get(CONF_SAFE_MODE, DEFAULT_SAFE_MODE), + ): bool, + vol.Optional( + CONF_ZEROCONF_DEFAULT_INTERFACE, + default=self.homekit_options.get( + CONF_ZEROCONF_DEFAULT_INTERFACE, + DEFAULT_ZEROCONF_DEFAULT_INTERFACE, + ), + ): bool, + } + ) + + return self.async_show_form( + step_id="advanced", data_schema=vol.Schema(schema_base) + ) + + async def async_step_exclude(self, user_input=None): + """Choose entities to exclude from the domain.""" + if user_input is not None: + self.homekit_options[CONF_FILTER] = { + CONF_INCLUDE_DOMAINS: self.homekit_options[CONF_INCLUDE_DOMAINS], + CONF_EXCLUDE_DOMAINS: self.homekit_options.get( + CONF_EXCLUDE_DOMAINS, [] + ), + CONF_INCLUDE_ENTITIES: self.homekit_options.get( + CONF_INCLUDE_ENTITIES, [] + ), + CONF_EXCLUDE_ENTITIES: user_input[CONF_EXCLUDE_ENTITIES], + } + return await self.async_step_advanced() + + entity_filter = self.homekit_options.get(CONF_FILTER, {}) + all_supported_entities = await self.hass.async_add_executor_job( + _get_entities_matching_domains, + self.hass, + self.homekit_options[CONF_INCLUDE_DOMAINS], + ) + data_schema = vol.Schema( + { + vol.Optional( + CONF_EXCLUDE_ENTITIES, + default=entity_filter.get(CONF_EXCLUDE_ENTITIES, []), + ): cv.multi_select(all_supported_entities), + } + ) + return self.async_show_form(step_id="exclude", data_schema=data_schema) + + async def async_step_init(self, user_input=None): + """Handle options flow.""" + if self.config_entry.source == SOURCE_IMPORT: + return await self.async_step_yaml(user_input) + + if user_input is not None: + self.homekit_options.update(user_input) + return await self.async_step_exclude() + + self.homekit_options = dict(self.config_entry.options) + entity_filter = self.homekit_options.get(CONF_FILTER, {}) + + data_schema = vol.Schema( + { + vol.Optional( + CONF_INCLUDE_DOMAINS, + default=entity_filter.get(CONF_INCLUDE_DOMAINS, []), + ): cv.multi_select(SUPPORTED_DOMAINS) + } + ) + return self.async_show_form(step_id="init", data_schema=data_schema) + + +def _get_entities_matching_domains(hass, domains): + """List entities in the given domains.""" + included_domains = set(domains) + entity_ids = [ + state.entity_id + for state in hass.states.all() + if (split_entity_id(state.entity_id))[0] in included_domains + ] + entity_ids.sort() + return entity_ids diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index f0224ce71f4..ab0c15ee9a7 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -1,13 +1,17 @@ """Constants used be the HomeKit component.""" + # #### Misc #### DEBOUNCE_TIMEOUT = 0.5 DEVICE_PRECISION_LEEWAY = 6 DOMAIN = "homekit" HOMEKIT_FILE = ".homekit.state" -HOMEKIT_NOTIFY_ID = 4663548 AID_STORAGE = "homekit-aid-allocations" HOMEKIT_PAIRING_QR = "homekit-pairing-qr" HOMEKIT_PAIRING_QR_SECRET = "homekit-pairing-qr-secret" +HOMEKIT = "homekit" +UNDO_UPDATE_LISTENER = "undo_update_listener" +SHUTDOWN_TIMEOUT = 30 +CONF_ENTRY_INDEX = "index" # #### Attributes #### ATTR_DISPLAY_NAME = "display_name" @@ -30,6 +34,7 @@ CONF_ZEROCONF_DEFAULT_INTERFACE = "zeroconf_default_interface" DEFAULT_AUTO_START = True DEFAULT_LOW_BATTERY_THRESHOLD = 20 DEFAULT_PORT = 51827 +DEFAULT_CONFIG_FLOW_PORT = 51828 DEFAULT_SAFE_MODE = False DEFAULT_ZEROCONF_DEFAULT_INTERFACE = False @@ -49,6 +54,7 @@ SERVICE_HOMEKIT_RESET_ACCESSORY = "reset_accessory" # #### String Constants #### BRIDGE_MODEL = "Bridge" BRIDGE_NAME = "Home Assistant Bridge" +SHORT_BRIDGE_NAME = "HASS Bridge" BRIDGE_SERIAL_NUMBER = "homekit.bridge" MANUFACTURER = "Home Assistant" @@ -203,3 +209,11 @@ HK_POSITION_STOPPED = 2 HK_NOT_CHARGING = 0 HK_CHARGING = 1 HK_NOT_CHARGABLE = 2 + +# ### Config Options ### +CONFIG_OPTIONS = [ + CONF_FILTER, + CONF_AUTO_START, + CONF_ZEROCONF_DEFAULT_INTERFACE, + CONF_SAFE_MODE, +] diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 482cef57ca7..27f83d996ad 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -5,5 +5,6 @@ "requirements": ["HAP-python==2.8.2","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1"], "dependencies": ["http"], "after_dependencies": ["logbook"], - "codeowners": ["@bdraco"] + "codeowners": ["@bdraco"], + "config_flow": true } diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json new file mode 100644 index 00000000000..ca5a67f5363 --- /dev/null +++ b/homeassistant/components/homekit/strings.json @@ -0,0 +1,54 @@ +{ + "title" : "HomeKit Bridge", + "options" : { + "step" : { + "yaml" : { + "title" : "Adjust HomeKit Bridge Options", + "description" : "This entry is controlled via YAML" + }, + "init" : { + "data" : { + "include_domains" : "[%key:component::homekit::config::step::user::data::include_domains%]" + }, + "description" : "Entities in the “Domains to include” will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title" : "Select domains to bridge." + }, + "exclude" : { + "data" : { + "exclude_entities" : "Entities to exclude" + }, + "description" : "Choose the entities that you do NOT want to be bridged.", + "title" : "Exclude entities in selected domains from bridge" + }, + "advanced" : { + "data" : { + "auto_start" : "[%key:component::homekit::config::step::user::data::auto_start%]", + "safe_mode" : "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface" : "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + }, + "description" : "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title" : "Advanced Configuration" + } + } + }, + "config" : { + "step" : { + "user" : { + "data" : { + "auto_start" : "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains" : "Domains to include" + }, + "description" : "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title" : "Activate HomeKit Bridge" + }, + "pairing": { + "title": "Pair HomeKit Bridge", + "description": "As soon as the {name} bridge is ready, pairing will be available in “Notifications” as “HomeKit Bridge Setup”." + } + }, + "abort" : { + "port_name_in_use" : "A bridge with the same name or port is already configured." + } + } + } + \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json new file mode 100644 index 00000000000..ca5a67f5363 --- /dev/null +++ b/homeassistant/components/homekit/translations/en.json @@ -0,0 +1,54 @@ +{ + "title" : "HomeKit Bridge", + "options" : { + "step" : { + "yaml" : { + "title" : "Adjust HomeKit Bridge Options", + "description" : "This entry is controlled via YAML" + }, + "init" : { + "data" : { + "include_domains" : "[%key:component::homekit::config::step::user::data::include_domains%]" + }, + "description" : "Entities in the “Domains to include” will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title" : "Select domains to bridge." + }, + "exclude" : { + "data" : { + "exclude_entities" : "Entities to exclude" + }, + "description" : "Choose the entities that you do NOT want to be bridged.", + "title" : "Exclude entities in selected domains from bridge" + }, + "advanced" : { + "data" : { + "auto_start" : "[%key:component::homekit::config::step::user::data::auto_start%]", + "safe_mode" : "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface" : "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + }, + "description" : "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title" : "Advanced Configuration" + } + } + }, + "config" : { + "step" : { + "user" : { + "data" : { + "auto_start" : "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains" : "Domains to include" + }, + "description" : "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title" : "Activate HomeKit Bridge" + }, + "pairing": { + "title": "Pair HomeKit Bridge", + "description": "As soon as the {name} bridge is ready, pairing will be available in “Notifications” as “HomeKit Bridge Setup”." + } + }, + "abort" : { + "port_name_in_use" : "A bridge with the same name or port is already configured." + } + } + } + \ No newline at end of file diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 987ba900bc8..25d1782b392 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -26,8 +26,7 @@ from homeassistant.const import ( STATE_OPENING, ) -from . import TYPES -from .accessories import HomeAccessory, debounce +from .accessories import TYPES, HomeAccessory, debounce from .const import ( CHAR_CURRENT_DOOR_STATE, CHAR_CURRENT_POSITION, diff --git a/homeassistant/components/homekit/type_fans.py b/homeassistant/components/homekit/type_fans.py index 291b3ffed90..b80a65eede1 100644 --- a/homeassistant/components/homekit/type_fans.py +++ b/homeassistant/components/homekit/type_fans.py @@ -27,8 +27,7 @@ from homeassistant.const import ( STATE_ON, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 8458c8351da..3f4c2518202 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -24,8 +24,7 @@ from homeassistant.const import ( STATE_ON, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_BRIGHTNESS, CHAR_COLOR_TEMPERATURE, diff --git a/homeassistant/components/homekit/type_locks.py b/homeassistant/components/homekit/type_locks.py index 0d2a19ef089..5697306bf32 100644 --- a/homeassistant/components/homekit/type_locks.py +++ b/homeassistant/components/homekit/type_locks.py @@ -6,8 +6,7 @@ from pyhap.const import CATEGORY_DOOR_LOCK from homeassistant.components.lock import DOMAIN, STATE_LOCKED, STATE_UNLOCKED from homeassistant.const import ATTR_CODE, ATTR_ENTITY_ID, STATE_UNKNOWN -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE, SERV_LOCK _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 78c11fc41f9..154355a0da3 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -37,8 +37,7 @@ from homeassistant.const import ( STATE_UNKNOWN, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_ACTIVE, CHAR_ACTIVE_IDENTIFIER, diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 59e10a42c29..8a2bb971cf1 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -18,8 +18,7 @@ from homeassistant.const import ( STATE_ALARM_TRIGGERED, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_CURRENT_SECURITY_STATE, CHAR_TARGET_SECURITY_STATE, diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 5bdf9a07f04..c37755bc1c5 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -11,8 +11,7 @@ from homeassistant.const import ( TEMP_CELSIUS, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_AIR_PARTICULATE_DENSITY, CHAR_AIR_QUALITY, diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index a1088f110e5..072c8681a50 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -27,8 +27,7 @@ from homeassistant.const import ( from homeassistant.core import split_entity_id from homeassistant.helpers.event import call_later -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_ACTIVE, CHAR_IN_USE, diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 0da92ef3dba..84cefced602 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -52,8 +52,7 @@ from homeassistant.const import ( UNIT_PERCENTAGE, ) -from . import TYPES -from .accessories import HomeAccessory +from .accessories import TYPES, HomeAccessory from .const import ( CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_CURRENT_HEATING_COOLING, diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index b8d98ad2304..3ccf73d3925 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -2,7 +2,9 @@ from collections import OrderedDict, namedtuple import io import logging +import os import secrets +import socket import pyqrcode import voluptuous as vol @@ -15,8 +17,9 @@ from homeassistant.const import ( CONF_TYPE, TEMP_CELSIUS, ) -from homeassistant.core import split_entity_id +from homeassistant.core import HomeAssistant, split_entity_id import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.storage import STORAGE_DIR import homeassistant.util.temperature as temp_util from .const import ( @@ -25,11 +28,12 @@ from .const import ( CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, DEFAULT_LOW_BATTERY_THRESHOLD, + DOMAIN, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, - HOMEKIT_NOTIFY_ID, + HOMEKIT_FILE, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, TYPE_FAUCET, @@ -42,6 +46,7 @@ from .const import ( _LOGGER = logging.getLogger(__name__) +MAX_PORT = 65535 BASIC_INFO_SCHEMA = vol.Schema( { @@ -210,7 +215,7 @@ class HomeKitSpeedMapping: return list(self.speed_ranges.keys())[0] -def show_setup_message(hass, pincode, uri): +def show_setup_message(hass, entry_id, bridge_name, pincode, uri): """Display persistent notification with setup information.""" pin = pincode.decode() _LOGGER.info("Pincode: %s", pin) @@ -220,23 +225,23 @@ def show_setup_message(hass, pincode, uri): url.svg(buffer, scale=5) pairing_secret = secrets.token_hex(32) - hass.data[HOMEKIT_PAIRING_QR] = buffer.getvalue() - hass.data[HOMEKIT_PAIRING_QR_SECRET] = pairing_secret + hass.data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR] = buffer.getvalue() + hass.data[DOMAIN][entry_id][HOMEKIT_PAIRING_QR_SECRET] = pairing_secret message = ( - f"To set up Home Assistant in the Home App, " + f"To set up {bridge_name} in the Home App, " f"scan the QR code or enter the following code:\n" f"### {pin}\n" - f"![image](/api/homekit/pairingqr?{pairing_secret})" + f"![image](/api/homekit/pairingqr?{entry_id}-{pairing_secret})" ) hass.components.persistent_notification.create( - message, "HomeKit Setup", HOMEKIT_NOTIFY_ID + message, "HomeKit Bridge Setup", entry_id ) -def dismiss_setup_message(hass): +def dismiss_setup_message(hass, entry_id): """Dismiss persistent notification and remove QR code.""" - hass.components.persistent_notification.dismiss(HOMEKIT_NOTIFY_ID) + hass.components.persistent_notification.dismiss(entry_id) def convert_to_float(state): @@ -268,3 +273,85 @@ def density_to_air_quality(density): if density <= 150: return 4 return 5 + + +def get_persist_filename_for_entry_id(entry_id: str): + """Determine the filename of the homekit state file.""" + return f"{DOMAIN}.{entry_id}.state" + + +def get_aid_storage_filename_for_entry_id(entry_id: str): + """Determine the ilename of homekit aid storage file.""" + return f"{DOMAIN}.{entry_id}.aids" + + +def get_persist_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str): + """Determine the path to the homekit state file.""" + return hass.config.path(STORAGE_DIR, get_persist_filename_for_entry_id(entry_id)) + + +def get_aid_storage_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str): + """Determine the path to the homekit aid storage file.""" + return hass.config.path( + STORAGE_DIR, get_aid_storage_filename_for_entry_id(entry_id) + ) + + +def migrate_filesystem_state_data_for_primary_imported_entry_id( + hass: HomeAssistant, entry_id: str +): + """Migrate the old paths to the storage directory.""" + legacy_persist_file_path = hass.config.path(HOMEKIT_FILE) + if os.path.exists(legacy_persist_file_path): + os.rename( + legacy_persist_file_path, get_persist_fullpath_for_entry_id(hass, entry_id) + ) + + legacy_aid_storage_path = hass.config.path(STORAGE_DIR, "homekit.aids") + if os.path.exists(legacy_aid_storage_path): + os.rename( + legacy_aid_storage_path, + get_aid_storage_fullpath_for_entry_id(hass, entry_id), + ) + + +def remove_state_files_for_entry_id(hass: HomeAssistant, entry_id: str): + """Remove the state files from disk.""" + persist_file_path = get_persist_fullpath_for_entry_id(hass, entry_id) + aid_storage_path = get_aid_storage_fullpath_for_entry_id(hass, entry_id) + os.unlink(persist_file_path) + if os.path.exists(aid_storage_path): + os.unlink(aid_storage_path) + return True + + +def _get_test_socket(): + """Create a socket to test binding ports.""" + test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + test_socket.setblocking(False) + test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return test_socket + + +def port_is_available(port: int): + """Check to see if a port is available.""" + test_socket = _get_test_socket() + try: + test_socket.bind(("", port)) + except OSError: + return False + + return True + + +def find_next_available_port(start_port: int): + """Find the next available port starting with the given port.""" + test_socket = _get_test_socket() + for port in range(start_port, MAX_PORT): + try: + test_socket.bind(("", port)) + return port + except OSError: + if port == MAX_PORT: + raise + continue diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6e259c2bf84..a65d1e2b52a 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -50,6 +50,7 @@ FLOWS = [ "harmony", "heos", "hisense_aehw4a1", + "homekit", "homekit_controller", "homematicip_cloud", "huawei_lte", diff --git a/homeassistant/helpers/entityfilter.py b/homeassistant/helpers/entityfilter.py index a9c3ab27ead..f8dd83ccfcc 100644 --- a/homeassistant/helpers/entityfilter.py +++ b/homeassistant/helpers/entityfilter.py @@ -12,7 +12,8 @@ CONF_EXCLUDE_DOMAINS = "exclude_domains" CONF_EXCLUDE_ENTITIES = "exclude_entities" -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], config[CONF_INCLUDE_ENTITIES], @@ -24,22 +25,21 @@ def _convert_filter(config: Dict[str, List[str]]) -> Callable[[str], bool]: return filt -FILTER_SCHEMA = vol.All( - vol.Schema( - { - vol.Optional(CONF_EXCLUDE_DOMAINS, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), - vol.Optional(CONF_EXCLUDE_ENTITIES, default=[]): cv.entity_ids, - vol.Optional(CONF_INCLUDE_DOMAINS, default=[]): vol.All( - cv.ensure_list, [cv.string] - ), - vol.Optional(CONF_INCLUDE_ENTITIES, default=[]): cv.entity_ids, - } - ), - _convert_filter, +BASE_FILTER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_EXCLUDE_DOMAINS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_EXCLUDE_ENTITIES, default=[]): cv.entity_ids, + vol.Optional(CONF_INCLUDE_DOMAINS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_INCLUDE_ENTITIES, default=[]): cv.entity_ids, + } ) +FILTER_SCHEMA = vol.All(BASE_FILTER_SCHEMA, convert_filter) + def generate_filter( include_domains: List[str], diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index c4b61f68833..e2fb79f56ce 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -453,7 +453,9 @@ def test_home_driver(): pin = b"123-45-678" with patch("pyhap.accessory_driver.AccessoryDriver.__init__") as mock_driver: - driver = HomeDriver("hass", address=ip_address, port=port, persist_file=path) + driver = HomeDriver( + "hass", "entry_id", "name", address=ip_address, port=port, persist_file=path + ) mock_driver.assert_called_with(address=ip_address, port=port, persist_file=path) driver.state = Mock(pincode=pin) @@ -467,7 +469,7 @@ def test_home_driver(): driver.pair("client_uuid", "client_public") mock_pair.assert_called_with("client_uuid", "client_public") - mock_dissmiss_msg.assert_called_with("hass") + mock_dissmiss_msg.assert_called_with("hass", "entry_id") # unpair with patch("pyhap.accessory_driver.AccessoryDriver.unpair") as mock_unpair, patch( @@ -476,4 +478,4 @@ def test_home_driver(): driver.unpair("client_uuid") mock_unpair.assert_called_with("client_uuid") - mock_show_msg.assert_called_with("hass", pin, "X-HM://0") + mock_show_msg.assert_called_with("hass", "entry_id", "name", pin, "X-HM://0") diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index 258f26e78a6..ff55ba9afa4 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -5,8 +5,8 @@ from zlib import adler32 import pytest from homeassistant.components.homekit.aidmanager import ( - AID_MANAGER_STORAGE_KEY, AccessoryAidStorage, + get_aid_storage_filename_for_entry_id, get_system_unique_id, ) from homeassistant.helpers import device_registry @@ -53,7 +53,7 @@ async def test_aid_generation(hass, device_reg, entity_reg): with patch( "homeassistant.components.homekit.aidmanager.AccessoryAidStorage.async_schedule_save" ): - aid_storage = AccessoryAidStorage(hass) + aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() for _ in range(0, 2): @@ -110,7 +110,7 @@ async def test_aid_adler32_collision(hass, device_reg, entity_reg): with patch( "homeassistant.components.homekit.aidmanager.AccessoryAidStorage.async_schedule_save" ): - aid_storage = AccessoryAidStorage(hass) + aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() seen_aids = set() @@ -129,8 +129,8 @@ async def test_aid_generation_no_unique_ids_handles_collision( hass, device_reg, entity_reg ): """Test colliding aids is stable.""" - - aid_storage = AccessoryAidStorage(hass) + config_entry = MockConfigEntry(domain="test", data={}) + aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() seen_aids = set() @@ -394,7 +394,7 @@ async def test_aid_generation_no_unique_ids_handles_collision( await aid_storage.async_save() await hass.async_block_till_done() - aid_storage = AccessoryAidStorage(hass) + aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() assert aid_storage.allocations == { @@ -620,6 +620,7 @@ async def test_aid_generation_no_unique_ids_handles_collision( "light.light99": 596247761, } - aid_storage_path = hass.config.path(STORAGE_DIR, AID_MANAGER_STORAGE_KEY) + aidstore = get_aid_storage_filename_for_entry_id(config_entry.entry_id) + aid_storage_path = hass.config.path(STORAGE_DIR, aidstore) if await hass.async_add_executor_job(os.path.exists, aid_storage_path): await hass.async_add_executor_job(os.unlink, aid_storage_path) diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py new file mode 100644 index 00000000000..0fd0a51fd51 --- /dev/null +++ b/tests/components/homekit/test_config_flow.py @@ -0,0 +1,259 @@ +"""Test the HomeKit config flow.""" +from asynctest import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.homekit.const import DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_NAME, CONF_PORT + +from tests.common import MockConfigEntry + + +def _mock_config_entry_with_options_populated(): + """Create a mock config entry with options populated.""" + return MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + options={ + "filter": { + "include_domains": [ + "fan", + "vacuum", + "media_player", + "climate", + "alarm_control_panel", + ], + "exclude_entities": ["climate.front_gate"], + }, + "auto_start": False, + "safe_mode": False, + "zeroconf_default_interface": True, + }, + ) + + +async def test_user_form(hass): + """Test we can setup a new instance.""" + 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.homekit.config_flow.find_next_available_port", + return_value=12345, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"auto_start": True, "include_domains": ["light"]}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "pairing" + + with patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.homekit.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure(result["flow_id"], {},) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"][:11] == "HASS Bridge" + bridge_name = (result3["title"].split(":"))[0] + assert result3["data"] == { + "auto_start": True, + "filter": { + "exclude_domains": [], + "exclude_entities": [], + "include_domains": ["light"], + "include_entities": [], + }, + "name": bridge_name, + "port": 12345, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_import(hass): + """Test we can import instance.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) + entry.add_to_hass(hass) + await hass.async_block_till_done() + + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "port_name_in_use" + + with patch( + "homeassistant.components.homekit.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.homekit.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={CONF_NAME: "othername", CONF_PORT: 56789}, + ) + + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "othername:56789" + assert result2["data"] == { + "name": "othername", + "port": 56789, + } + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 2 + + +async def test_options_flow_advanced(hass): + """Test config flow options.""" + + config_entry = _mock_config_entry_with_options_populated() + config_entry.add_to_hass(hass) + + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": True} + ) + + 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={"include_domains": ["fan", "vacuum", "climate"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "exclude" + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={"exclude_entities": ["climate.old"]}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "advanced" + + with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], + user_input={ + "auto_start": True, + "safe_mode": True, + "zeroconf_default_interface": False, + }, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == { + "auto_start": True, + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + "safe_mode": True, + "zeroconf_default_interface": False, + } + + +async def test_options_flow_basic(hass): + """Test config flow options.""" + + config_entry = _mock_config_entry_with_options_populated() + config_entry.add_to_hass(hass) + + hass.states.async_set("climate.old", "off") + await hass.async_block_till_done() + + result = await hass.config_entries.options.async_init( + config_entry.entry_id, context={"show_advanced_options": False} + ) + + 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={"include_domains": ["fan", "vacuum", "climate"]}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "exclude" + + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={"exclude_entities": ["climate.old"]}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["step_id"] == "advanced" + + with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): + result3 = await hass.config_entries.options.async_configure( + result2["flow_id"], + user_input={"safe_mode": True, "zeroconf_default_interface": False}, + ) + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == { + "auto_start": False, + "filter": { + "exclude_domains": [], + "exclude_entities": ["climate.old"], + "include_domains": ["fan", "vacuum", "climate"], + "include_entities": [], + }, + "safe_mode": True, + "zeroconf_default_interface": False, + } + + +async def test_options_flow_blocked_when_from_yaml(hass): + """Test config flow options.""" + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + options={ + "auto_start": True, + "filter": { + "include_domains": [ + "fan", + "vacuum", + "media_player", + "climate", + "alarm_control_panel", + ], + "exclude_entities": ["climate.front_gate"], + }, + "safe_mode": False, + "zeroconf_default_interface": True, + }, + source=SOURCE_IMPORT, + ) + 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"] == "yaml" + + with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): + result2 = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={}, + ) + assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 08a04d5b88e..286fe51535e 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -5,7 +5,7 @@ import pytest import homeassistant.components.climate as climate import homeassistant.components.cover as cover -from homeassistant.components.homekit import TYPES, get_accessory +from homeassistant.components.homekit.accessories import TYPES, get_accessory from homeassistant.components.homekit.const import ( CONF_FEATURE_LIST, FEATURE_ON_OFF, @@ -17,6 +17,7 @@ from homeassistant.components.homekit.const import ( TYPE_VALVE, ) import homeassistant.components.media_player.const as media_player_c +import homeassistant.components.vacuum as vacuum from homeassistant.const import ( ATTR_CODE, ATTR_DEVICE_CLASS, @@ -239,3 +240,27 @@ def test_type_switches(type_name, entity_id, state, attrs, config): entity_state = State(entity_id, state, attrs) get_accessory(None, None, entity_state, 2, config) assert mock_type.called + + +@pytest.mark.parametrize( + "type_name, entity_id, state, attrs", + [ + ( + "DockVacuum", + "vacuum.dock_vacuum", + "docked", + { + ATTR_SUPPORTED_FEATURES: vacuum.SUPPORT_START + | vacuum.SUPPORT_RETURN_HOME + }, + ), + ("Switch", "vacuum.basic_vacuum", "off", {},), + ], +) +def test_type_vacuum(type_name, entity_id, state, attrs): + """Test if vacuum types are associated correctly.""" + mock_type = Mock() + with patch.dict(TYPES, {type_name: mock_type}): + entity_state = State(entity_id, state, attrs) + get_accessory(None, None, entity_state, 2, {}) + assert mock_type.called diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 8a1d911ef04..e5bee83a0eb 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,10 +1,11 @@ """Tests for the HomeKit component.""" +import os +from typing import Dict from unittest.mock import ANY, Mock, patch import pytest from zeroconf import InterfaceChoice -from homeassistant import setup from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING from homeassistant.components.homekit import ( MAX_DEVICES, @@ -19,6 +20,7 @@ from homeassistant.components.homekit.const import ( AID_STORAGE, BRIDGE_NAME, CONF_AUTO_START, + CONF_ENTRY_INDEX, CONF_SAFE_MODE, CONF_ZEROCONF_DEFAULT_INTERFACE, DEFAULT_PORT, @@ -28,6 +30,11 @@ from homeassistant.components.homekit.const import ( SERVICE_HOMEKIT_RESET_ACCESSORY, SERVICE_HOMEKIT_START, ) +from homeassistant.components.homekit.util import ( + get_aid_storage_fullpath_for_entry_id, + get_persist_fullpath_for_entry_id, +) +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, @@ -42,13 +49,17 @@ from homeassistant.const import ( from homeassistant.core import State from homeassistant.helpers import device_registry from homeassistant.helpers.entityfilter import generate_filter +from homeassistant.helpers.storage import STORAGE_DIR +from homeassistant.setup import async_setup_component +from homeassistant.util import json as json_util + +from .util import PATH_HOMEKIT, async_init_entry, async_init_integration from tests.async_mock import AsyncMock from tests.common import MockConfigEntry, mock_device_registry, mock_registry from tests.components.homekit.common import patch_debounce IP_ADDRESS = "127.0.0.1" -PATH_HOMEKIT = "homeassistant.components.homekit" @pytest.fixture @@ -73,11 +84,31 @@ def debounce_patcher(): async def test_setup_min(hass): """Test async_setup with min config options.""" + + 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") as mock_homekit: - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() mock_homekit.assert_any_call( - hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, {}, DEFAULT_SAFE_MODE, None, None + hass, + BRIDGE_NAME, + DEFAULT_PORT, + None, + ANY, + {}, + DEFAULT_SAFE_MODE, + None, + None, + entry.entry_id, ) assert mock_homekit().setup.called is True @@ -86,26 +117,27 @@ async def test_setup_min(hass): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - mock_homekit().async_start.assert_called_with(ANY) + mock_homekit().async_start.assert_called() async def test_setup_auto_start_disabled(hass): """Test async_setup with auto start disabled and test service calls.""" - config = { - DOMAIN: { + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "Test Name", CONF_PORT: 11111, CONF_IP_ADDRESS: "172.0.0.0"}, + options={ CONF_AUTO_START: False, - CONF_NAME: "Test Name", - CONF_PORT: 11111, - CONF_IP_ADDRESS: "172.0.0.0", CONF_SAFE_MODE: DEFAULT_SAFE_MODE, CONF_ZEROCONF_DEFAULT_INTERFACE: True, - } - } + }, + ) + entry.add_to_hass(hass) with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() - assert await setup.async_setup_component(hass, DOMAIN, config) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() mock_homekit.assert_any_call( hass, @@ -117,6 +149,7 @@ async def test_setup_auto_start_disabled(hass): DEFAULT_SAFE_MODE, None, InterfaceChoice.Default, + entry.entry_id, ) assert mock_homekit().setup.called is True @@ -148,7 +181,23 @@ async def test_setup_auto_start_disabled(hass): async def test_homekit_setup(hass, hk_driver): """Test setup of bridge and driver.""" - homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, DEFAULT_SAFE_MODE) + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + source=SOURCE_IMPORT, + ) + homekit = HomeKit( + hass, + BRIDGE_NAME, + DEFAULT_PORT, + None, + {}, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) with patch( f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver @@ -156,10 +205,12 @@ async def test_homekit_setup(hass, hk_driver): mock_ip.return_value = IP_ADDRESS await hass.async_add_executor_job(homekit.setup) - path = hass.config.path(HOMEKIT_FILE) + path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) assert isinstance(homekit.bridge, HomeBridge) mock_driver.assert_called_with( hass, + entry.entry_id, + BRIDGE_NAME, address=IP_ADDRESS, port=DEFAULT_PORT, persist_file=path, @@ -174,17 +225,36 @@ async def test_homekit_setup(hass, hk_driver): async def test_homekit_setup_ip_address(hass, hk_driver): """Test setup with given IP address.""" - homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, "172.0.0.0", {}, {}, None) + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + source=SOURCE_IMPORT, + ) + homekit = HomeKit( + hass, + BRIDGE_NAME, + DEFAULT_PORT, + "172.0.0.0", + {}, + {}, + None, + None, + interface_choice=None, + entry_id=entry.entry_id, + ) + path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) with patch( f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver ) as mock_driver: await hass.async_add_executor_job(homekit.setup) mock_driver.assert_called_with( hass, + entry.entry_id, + BRIDGE_NAME, address="172.0.0.0", port=DEFAULT_PORT, - persist_file=ANY, + persist_file=path, advertised_address=None, interface_choice=None, ) @@ -192,19 +262,36 @@ async def test_homekit_setup_ip_address(hass, hk_driver): async def test_homekit_setup_advertise_ip(hass, hk_driver): """Test setup with given IP address to advertise.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + source=SOURCE_IMPORT, + ) homekit = HomeKit( - hass, BRIDGE_NAME, DEFAULT_PORT, "0.0.0.0", {}, {}, None, "192.168.1.100" + hass, + BRIDGE_NAME, + DEFAULT_PORT, + "0.0.0.0", + {}, + {}, + None, + "192.168.1.100", + interface_choice=None, + entry_id=entry.entry_id, ) + path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) with patch( f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver ) as mock_driver: await hass.async_add_executor_job(homekit.setup) mock_driver.assert_called_with( hass, + entry.entry_id, + BRIDGE_NAME, address="0.0.0.0", port=DEFAULT_PORT, - persist_file=ANY, + persist_file=path, advertised_address="192.168.1.100", interface_choice=None, ) @@ -212,6 +299,11 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): async def test_homekit_setup_interface_choice(hass, hk_driver): """Test setup with interface choice of Default.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + source=SOURCE_IMPORT, + ) homekit = HomeKit( hass, BRIDGE_NAME, @@ -222,17 +314,21 @@ async def test_homekit_setup_interface_choice(hass, hk_driver): None, None, InterfaceChoice.Default, + entry_id=entry.entry_id, ) + path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) with patch( f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver ) as mock_driver: await hass.async_add_executor_job(homekit.setup) mock_driver.assert_called_with( hass, + entry.entry_id, + BRIDGE_NAME, address="0.0.0.0", port=DEFAULT_PORT, - persist_file=ANY, + persist_file=path, advertised_address=None, interface_choice=InterfaceChoice.Default, ) @@ -240,7 +336,23 @@ async def test_homekit_setup_interface_choice(hass, hk_driver): async def test_homekit_setup_safe_mode(hass, hk_driver): """Test if safe_mode flag is set.""" - homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}, True, None) + entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_NAME: "mock_name", CONF_PORT: 12345}, + source=SOURCE_IMPORT, + ) + homekit = HomeKit( + hass, + BRIDGE_NAME, + DEFAULT_PORT, + None, + {}, + {}, + True, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) with patch(f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver): await hass.async_add_executor_job(homekit.setup) @@ -249,12 +361,25 @@ async def test_homekit_setup_safe_mode(hass, hk_driver): async def test_homekit_add_accessory(hass): """Add accessory if config exists and get_acc returns an accessory.""" - homekit = HomeKit(hass, None, None, None, lambda entity_id: True, {}, None, None) + entry = await async_init_integration(hass) + + homekit = HomeKit( + hass, + None, + None, + None, + lambda entity_id: True, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() homekit.bridge.accessories = range(10) - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await async_init_integration(hass) with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, "acc", None] @@ -273,7 +398,20 @@ async def test_homekit_add_accessory(hass): async def test_homekit_remove_accessory(hass): """Remove accessory from bridge.""" - homekit = HomeKit("hass", None, None, None, lambda entity_id: True, {}, None, None) + entry = await async_init_integration(hass) + + homekit = HomeKit( + hass, + None, + None, + None, + lambda entity_id: True, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.driver = "driver" homekit.bridge = mock_bridge = Mock() mock_bridge.accessories = {"light.demo": "acc"} @@ -285,10 +423,21 @@ async def test_homekit_remove_accessory(hass): async def test_homekit_entity_filter(hass): """Test the entity filter.""" - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + entry = await async_init_integration(hass) entity_filter = generate_filter(["cover"], ["demo.test"], [], []) - homekit = HomeKit(hass, None, None, None, entity_filter, {}, None, None) + homekit = HomeKit( + hass, + None, + None, + None, + entity_filter, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.bridge = Mock() homekit.bridge.accessories = {} @@ -309,8 +458,21 @@ async def test_homekit_entity_filter(hass): async def test_homekit_start(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" + entry = await async_init_integration(hass) + pin = b"123-45-678" - homekit = HomeKit(hass, None, None, None, {}, {"cover.demo": {}}, None, None) + homekit = HomeKit( + hass, + None, + None, + None, + {}, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver @@ -330,7 +492,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): await hass.async_block_till_done() mock_add_acc.assert_called_with(state) - mock_setup_msg.assert_called_with(hass, pin, ANY) + mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY) hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -345,11 +507,25 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" pin = b"123-45-678" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], []) - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await async_init_entry(hass, entry) + homekit = HomeKit( + hass, + None, + None, + None, + entity_filter, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) - homekit = HomeKit(hass, None, None, None, entity_filter, {}, None, None) homekit.bridge = Mock() homekit.bridge.accessories = [] homekit.driver = hk_driver @@ -367,7 +543,7 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p await homekit.async_start() await hass.async_block_till_done() - mock_setup_msg.assert_called_with(hass, pin, ANY) + mock_setup_msg.assert_called_with(hass, entry.entry_id, None, pin, ANY) hk_driver_add_acc.assert_called_with(homekit.bridge) assert hk_driver_start.called assert homekit.status == STATUS_RUNNING @@ -381,10 +557,23 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p async def test_homekit_stop(hass): """Test HomeKit stop method.""" - homekit = HomeKit(hass, None, None, None, None, None, None) + entry = await async_init_integration(hass) + + homekit = HomeKit( + hass, + None, + None, + None, + {}, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.driver = Mock() - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await async_init_integration(hass) assert homekit.status == STATUS_READY await homekit.async_stop() @@ -406,8 +595,23 @@ async def test_homekit_stop(hass): async def test_homekit_reset_accessories(hass): """Test adding too many accessories to HomeKit.""" + + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) entity_id = "light.demo" - homekit = HomeKit(hass, None, None, None, {}, {entity_id: {}}, None) + homekit = HomeKit( + hass, + None, + None, + None, + {}, + {entity_id: {}}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.bridge = Mock() homekit.bridge.accessories = {} @@ -415,11 +619,14 @@ async def test_homekit_reset_accessories(hass): f"{PATH_HOMEKIT}.HomeKit.setup" ), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch( "pyhap.accessory_driver.AccessoryDriver.config_changed" - ) as hk_driver_config_changed: + ) as hk_driver_config_changed, patch( + "pyhap.accessory_driver.AccessoryDriver.start" + ): + await async_init_entry(hass, entry) - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - - aid = hass.data[AID_STORAGE].get_or_allocate_aid_for_entity_id(entity_id) + aid = hass.data[DOMAIN][entry.entry_id][ + AID_STORAGE + ].get_or_allocate_aid_for_entity_id(entity_id) homekit.bridge.accessories = {aid: "acc"} homekit.status = STATUS_RUNNING @@ -438,10 +645,22 @@ async def test_homekit_reset_accessories(hass): async def test_homekit_too_many_accessories(hass, hk_driver): """Test adding too many accessories to HomeKit.""" + entry = await async_init_integration(hass) entity_filter = generate_filter(["cover", "light"], ["demo.test"], [], []) - homekit = HomeKit(hass, None, None, None, entity_filter, {}, None, None) + homekit = HomeKit( + hass, + None, + None, + None, + entity_filter, + {}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.bridge = Mock() # The bridge itself counts as an accessory homekit.bridge.accessories = range(MAX_DEVICES) @@ -463,9 +682,20 @@ async def test_homekit_finds_linked_batteries( hass, hk_driver, debounce_patcher, device_reg, entity_reg ): """Test HomeKit start method.""" - assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + entry = await async_init_integration(hass) - homekit = HomeKit(hass, None, None, None, {}, {"light.demo": {}}, None, None) + homekit = HomeKit( + hass, + None, + None, + None, + {}, + {"light.demo": {}}, + DEFAULT_SAFE_MODE, + advertise_ip=None, + interface_choice=None, + entry_id=entry.entry_id, + ) homekit.driver = hk_driver homekit._filter = Mock(return_value=True) homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge") @@ -526,3 +756,132 @@ async def test_homekit_finds_linked_batteries( "linked_battery_sensor": "sensor.light_battery", }, ) + + +async def test_setup_imported(hass): + """Test async_setup with imported config options.""" + legacy_persist_file_path = hass.config.path(HOMEKIT_FILE) + legacy_aid_storage_path = hass.config.path(STORAGE_DIR, "homekit.aids") + legacy_homekit_state_contents = {"homekit.state": 1} + legacy_homekit_aids_contents = {"homekit.aids": 1} + await hass.async_add_executor_job( + _write_data, legacy_persist_file_path, legacy_homekit_state_contents + ) + await hass.async_add_executor_job( + _write_data, legacy_aid_storage_path, legacy_homekit_aids_contents + ) + + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_IMPORT, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT, CONF_ENTRY_INDEX: 0}, + options={}, + ) + entry.add_to_hass(hass) + + with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: + mock_homekit.return_value = homekit = Mock() + type(homekit).async_start = AsyncMock() + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + mock_homekit.assert_any_call( + hass, + BRIDGE_NAME, + DEFAULT_PORT, + None, + ANY, + {}, + DEFAULT_SAFE_MODE, + None, + None, + entry.entry_id, + ) + assert mock_homekit().setup.called is True + + # Test auto start enabled + mock_homekit.reset_mock() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + mock_homekit().async_start.assert_called() + + migrated_persist_file_path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) + assert ( + await hass.async_add_executor_job( + json_util.load_json, migrated_persist_file_path + ) + == legacy_homekit_state_contents + ) + os.unlink(migrated_persist_file_path) + migrated_aid_file_path = get_aid_storage_fullpath_for_entry_id(hass, entry.entry_id) + assert ( + await hass.async_add_executor_job(json_util.load_json, migrated_aid_file_path) + == legacy_homekit_aids_contents + ) + os.unlink(migrated_aid_file_path) + + +async def test_yaml_updates_update_config_entry_for_name(hass): + """Test async_setup with imported config.""" + + entry = MockConfigEntry( + domain=DOMAIN, + source=SOURCE_IMPORT, + data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT}, + options={}, + ) + entry.add_to_hass(hass) + + 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: BRIDGE_NAME, CONF_PORT: 12345}} + ) + await hass.async_block_till_done() + + mock_homekit.assert_any_call( + hass, + BRIDGE_NAME, + 12345, + None, + ANY, + {}, + DEFAULT_SAFE_MODE, + None, + None, + entry.entry_id, + ) + assert mock_homekit().setup.called is True + + # Test auto start enabled + mock_homekit.reset_mock() + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + mock_homekit().async_start.assert_called() + + +async def test_raise_config_entry_not_ready(hass): + """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( + "homeassistant.components.homekit.port_is_available", return_value=False, + ): + assert not await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + +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)) + json_util.save_json(path, data) diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index 5fe8c438ca1..cb2de7264a8 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -348,6 +348,7 @@ async def test_media_player_television_basic(hass, hk_driver, events, caplog): await hass.async_block_till_done() acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) await acc.run_handler() + await hass.async_block_till_done() assert acc.chars_tv == [] assert acc.chars_speaker == [] diff --git a/tests/components/homekit/test_type_security_systems.py b/tests/components/homekit/test_type_security_systems.py index 690cd8f318f..b139fac3657 100644 --- a/tests/components/homekit/test_type_security_systems.py +++ b/tests/components/homekit/test_type_security_systems.py @@ -28,6 +28,7 @@ async def test_switch_set_state(hass, hk_driver, events): await hass.async_block_till_done() acc = SecuritySystem(hass, hk_driver, "SecuritySystem", entity_id, 2, config) await acc.run_handler() + await hass.async_block_till_done() assert acc.aid == 2 assert acc.category == 11 # AlarmSystem diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 41b134c10a5..2c8c93cee4c 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -7,9 +7,10 @@ from homeassistant.components.homekit.const import ( CONF_FEATURE_LIST, CONF_LINKED_BATTERY_SENSOR, CONF_LOW_BATTERY_THRESHOLD, + DEFAULT_CONFIG_FLOW_PORT, + DOMAIN, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, - HOMEKIT_NOTIFY_ID, HOMEKIT_PAIRING_QR, HOMEKIT_PAIRING_QR_SECRET, TYPE_FAUCET, @@ -25,6 +26,8 @@ from homeassistant.components.homekit.util import ( convert_to_float, density_to_air_quality, dismiss_setup_message, + find_next_available_port, + port_is_available, show_setup_message, temperature_to_homekit, temperature_to_states, @@ -34,7 +37,7 @@ from homeassistant.components.homekit.util import ( from homeassistant.components.persistent_notification import ( ATTR_MESSAGE, ATTR_NOTIFICATION_ID, - DOMAIN, + DOMAIN as PERSISTENT_NOTIFICATION_DOMAIN, ) from homeassistant.const import ( ATTR_CODE, @@ -47,6 +50,8 @@ from homeassistant.const import ( ) from homeassistant.core import State +from .util import async_init_integration + from tests.common import async_mock_service @@ -199,27 +204,36 @@ async def test_show_setup_msg(hass): """Test show setup message as persistence notification.""" pincode = b"123-45-678" - call_create_notification = async_mock_service(hass, DOMAIN, "create") + entry = await async_init_integration(hass) + assert entry - await hass.async_add_executor_job(show_setup_message, hass, pincode, "X-HM://0") + call_create_notification = async_mock_service( + hass, PERSISTENT_NOTIFICATION_DOMAIN, "create" + ) + + await hass.async_add_executor_job( + show_setup_message, hass, entry.entry_id, "bridge_name", pincode, "X-HM://0" + ) await hass.async_block_till_done() - assert hass.data[HOMEKIT_PAIRING_QR_SECRET] - assert hass.data[HOMEKIT_PAIRING_QR] + assert hass.data[DOMAIN][entry.entry_id][HOMEKIT_PAIRING_QR_SECRET] + assert hass.data[DOMAIN][entry.entry_id][HOMEKIT_PAIRING_QR] assert call_create_notification - assert call_create_notification[0].data[ATTR_NOTIFICATION_ID] == HOMEKIT_NOTIFY_ID + assert call_create_notification[0].data[ATTR_NOTIFICATION_ID] == entry.entry_id assert pincode.decode() in call_create_notification[0].data[ATTR_MESSAGE] async def test_dismiss_setup_msg(hass): """Test dismiss setup message.""" - call_dismiss_notification = async_mock_service(hass, DOMAIN, "dismiss") + call_dismiss_notification = async_mock_service( + hass, PERSISTENT_NOTIFICATION_DOMAIN, "dismiss" + ) - await hass.async_add_executor_job(dismiss_setup_message, hass) + await hass.async_add_executor_job(dismiss_setup_message, hass, "entry_id") await hass.async_block_till_done() assert call_dismiss_notification - assert call_dismiss_notification[0].data[ATTR_NOTIFICATION_ID] == HOMEKIT_NOTIFY_ID + assert call_dismiss_notification[0].data[ATTR_NOTIFICATION_ID] == "entry_id" def test_homekit_speed_mapping(): @@ -277,3 +291,13 @@ def test_speed_to_states(): assert speed_mapping.speed_to_states(66) == "low" assert speed_mapping.speed_to_states(67) == "high" assert speed_mapping.speed_to_states(100) == "high" + + +async def test_port_is_available(hass): + """Test we can get an available port and it is actually available.""" + next_port = await hass.async_add_executor_job( + find_next_available_port, DEFAULT_CONFIG_FLOW_PORT + ) + assert next_port + + assert await hass.async_add_executor_job(port_is_available, next_port) diff --git a/tests/components/homekit/util.py b/tests/components/homekit/util.py new file mode 100644 index 00000000000..0abf3007c04 --- /dev/null +++ b/tests/components/homekit/util.py @@ -0,0 +1,34 @@ +"""Test util for the homekit integration.""" + +from asynctest import patch + +from homeassistant.components.homekit.const import DOMAIN +from homeassistant.const import CONF_NAME, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +PATH_HOMEKIT = "homeassistant.components.homekit" + + +async def async_init_integration(hass: HomeAssistant) -> MockConfigEntry: + """Set up the homekit integration in Home Assistant.""" + + with patch(f"{PATH_HOMEKIT}.HomeKit.async_start"): + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + return entry + + +async def async_init_entry(hass: HomeAssistant, entry: MockConfigEntry): + """Set up the homekit integration in Home Assistant.""" + + with patch(f"{PATH_HOMEKIT}.HomeKit.async_start"): + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + return entry From 3e06e6b4b37bd10660872fbce4e3e892fedbc90b Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Fri, 1 May 2020 00:05:45 -0400 Subject: [PATCH 175/511] Don't attempt to set Vizio is_volume_muted property if Vizio API doesn't provide muted state (#34782) --- .../components/vizio/media_player.py | 15 +++++++----- tests/components/vizio/test_media_player.py | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 2a81ed1eaad..67bb3d3633c 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -132,7 +132,7 @@ class VizioDevice(MediaPlayerEntity): self._state = None self._volume_level = None self._volume_step = config_entry.options[CONF_VOLUME_STEP] - self._is_muted = None + self._is_volume_muted = None self._current_input = None self._current_app = None self._current_app_config = None @@ -190,7 +190,7 @@ class VizioDevice(MediaPlayerEntity): if not is_on: self._state = STATE_OFF self._volume_level = None - self._is_muted = None + self._is_volume_muted = None self._current_input = None self._available_inputs = None self._current_app = None @@ -207,7 +207,10 @@ class VizioDevice(MediaPlayerEntity): ) if audio_settings is not None: self._volume_level = float(audio_settings["volume"]) / self._max_volume - self._is_muted = audio_settings["mute"].lower() == "on" + if "mute" in audio_settings: + self._is_volume_muted = audio_settings["mute"].lower() == "on" + else: + self._is_volume_muted = None if VIZIO_SOUND_MODE in audio_settings: self._supported_commands |= SUPPORT_SELECT_SOUND_MODE @@ -324,7 +327,7 @@ class VizioDevice(MediaPlayerEntity): @property def is_volume_muted(self): """Boolean if volume is currently muted.""" - return self._is_muted + return self._is_volume_muted @property def source(self) -> str: @@ -428,10 +431,10 @@ class VizioDevice(MediaPlayerEntity): """Mute the volume.""" if mute: await self._device.mute_on() - self._is_muted = True + self._is_volume_muted = True else: await self._device.mute_off() - self._is_muted = False + self._is_volume_muted = False async def async_media_previous_track(self) -> None: """Send previous channel command.""" diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index aaf802af7ec..b4a2148b8da 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -621,3 +621,26 @@ async def test_setup_with_no_running_app( assert attr["source"] == "CAST" assert "app_id" not in attr assert "app_name" not in attr + + +async def test_setup_tv_without_mute( + hass: HomeAssistantType, + vizio_connect: pytest.fixture, + vizio_update: pytest.fixture, +) -> None: + """Test Vizio TV entity setup when mute property isn't returned by Vizio API.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + data=vol.Schema(VIZIO_SCHEMA)(MOCK_USER_VALID_TV_CONFIG), + unique_id=UNIQUE_ID, + ) + + async with _cm_for_test_setup_without_apps( + {"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2)}, STATE_ON, + ): + await _add_config_entry_to_hass(hass, config_entry) + + attr = _get_attr_and_assert_base_attr(hass, DEVICE_CLASS_TV, STATE_ON) + _assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV) + assert "sound_mode" not in attr + assert "is_volume_muted" not in attr From 7b90cbd2b27f059d7bb5afaa637615f84153862e Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 1 May 2020 06:06:16 +0200 Subject: [PATCH 176/511] UniFi - Disconnected clients wrongfully marked as wired not created (#34986) --- homeassistant/components/unifi/device_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 7bc1bbd3197..0b71a7d517e 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -80,7 +80,7 @@ def add_entities(controller, async_add_entities): if tracker_class is UniFiClientTracker: - if item.is_wired: + if mac not in controller.wireless_clients: if not controller.option_track_wired_clients: continue else: From 1f66821256d8911f79c25afb825f688234c86bd0 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 30 Apr 2020 23:08:15 -0500 Subject: [PATCH 177/511] Support num_repeats for directv remote (#34982) --- homeassistant/components/directv/remote.py | 23 ++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/directv/remote.py b/homeassistant/components/directv/remote.py index 776ce7a229b..9665b0aea17 100644 --- a/homeassistant/components/directv/remote.py +++ b/homeassistant/components/directv/remote.py @@ -5,7 +5,7 @@ from typing import Any, Callable, Iterable, List from directv import DIRECTV, DIRECTVError -from homeassistant.components.remote import RemoteEntity +from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -95,12 +95,15 @@ class DIRECTVRemote(DIRECTVEntity, RemoteEntity): blue, chanup, chandown, prev, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, dash, enter """ - for single_command in command: - try: - await self.dtv.remote(single_command, self._address) - except DIRECTVError: - _LOGGER.exception( - "Sending command %s to device %s failed", - single_command, - self._device_id, - ) + num_repeats = kwargs[ATTR_NUM_REPEATS] + + for _ in range(num_repeats): + for single_command in command: + try: + await self.dtv.remote(single_command, self._address) + except DIRECTVError: + _LOGGER.exception( + "Sending command %s to device %s failed", + single_command, + self._device_id, + ) From bb08959131ebca8758a6d43d8ea169fe804c78e1 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 30 Apr 2020 23:08:32 -0500 Subject: [PATCH 178/511] Support num_repeats for roku remote (#34981) --- homeassistant/components/roku/remote.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 3a9adf3518c..22102ac8282 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -7,7 +7,7 @@ from requests.exceptions import ( ) from roku import RokuException -from homeassistant.components.remote import RemoteEntity +from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -84,8 +84,11 @@ class RokuRemote(RemoteEntity): def send_command(self, command, **kwargs): """Send a command to one device.""" - for single_command in command: - if not hasattr(self.roku, single_command): - continue + num_repeats = kwargs[ATTR_NUM_REPEATS] - getattr(self.roku, single_command)() + for _ in range(num_repeats): + for single_command in command: + if not hasattr(self.roku, single_command): + continue + + getattr(self.roku, single_command)() From 225059f8eaf337d24ff5c2c1e81d6034990450f9 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Thu, 30 Apr 2020 21:13:45 -0700 Subject: [PATCH 179/511] Fix roomba not reporting error (#34996) --- homeassistant/components/roomba/irobot_base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index cb422616e5a..86e9b0da6b5 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -3,6 +3,7 @@ import asyncio import logging from homeassistant.components.vacuum import ( + ATTR_STATUS, STATE_CLEANING, STATE_DOCKED, STATE_ERROR, @@ -16,6 +17,7 @@ from homeassistant.components.vacuum import ( SUPPORT_SEND_COMMAND, SUPPORT_START, SUPPORT_STATE, + SUPPORT_STATUS, SUPPORT_STOP, StateVacuumEntity, ) @@ -40,6 +42,7 @@ SUPPORT_IROBOT = ( | SUPPORT_SEND_COMMAND | SUPPORT_START | SUPPORT_STATE + | SUPPORT_STATUS | SUPPORT_STOP | SUPPORT_LOCATE ) @@ -143,7 +146,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): state = STATE_MAP[phase] except KeyError: return STATE_ERROR - if cycle != "none" and state != STATE_CLEANING and state != STATE_RETURNING: + if cycle != "none" and (state == STATE_IDLE or state == STATE_DOCKED): state = STATE_PAUSED return state @@ -173,6 +176,9 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # Set properties that are to appear in the GUI state_attrs = {ATTR_SOFTWARE_VERSION: software_version} + # Set legacy status to avoid break changes + state_attrs[ATTR_STATUS] = self.vacuum.current_state + # Only add cleaning time and cleaned area attrs when the vacuum is # currently on if self.state == STATE_CLEANING: From 8f9467492d46b6126ac6f9392a25fda68897e8ef Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 30 Apr 2020 21:34:51 -0700 Subject: [PATCH 180/511] Remove some passings of loop (#34995) --- homeassistant/components/rflink/__init__.py | 2 +- homeassistant/components/shell_command/__init__.py | 2 -- homeassistant/components/tradfri/__init__.py | 1 - homeassistant/components/tradfri/config_flow.py | 2 +- homeassistant/helpers/aiohttp_client.py | 9 ++------- 5 files changed, 4 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/rflink/__init__.py b/homeassistant/components/rflink/__init__.py index 542c63f3b7a..68b6d841654 100644 --- a/homeassistant/components/rflink/__init__.py +++ b/homeassistant/components/rflink/__init__.py @@ -230,7 +230,7 @@ async def async_setup(hass, config): ) try: - with async_timeout.timeout(CONNECTION_TIMEOUT, loop=hass.loop): + with async_timeout.timeout(CONNECTION_TIMEOUT): transport, protocol = await connection except ( diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index 89a1a20e8e4..dc9fd8769d6 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -56,7 +56,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: # pylint: disable=no-member create_process = asyncio.subprocess.create_subprocess_shell( cmd, - loop=hass.loop, stdin=None, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -69,7 +68,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: # pylint: disable=no-member create_process = asyncio.subprocess.create_subprocess_exec( *shlexed_cmd, - loop=hass.loop, stdin=None, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index a797607e243..cef22c636c1 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -100,7 +100,6 @@ async def async_setup_entry(hass, entry): entry.data[CONF_HOST], psk_id=entry.data[CONF_IDENTITY], psk=entry.data[CONF_KEY], - loop=hass.loop, ) async def on_hass_stop(event): diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py index 1663ba675a3..2ade04cff55 100644 --- a/homeassistant/components/tradfri/config_flow.py +++ b/homeassistant/components/tradfri/config_flow.py @@ -178,7 +178,7 @@ async def get_gateway_info(hass, host, identity, key): """Return info for the gateway.""" try: - factory = APIFactory(host, psk_id=identity, psk=key, loop=hass.loop) + factory = APIFactory(host, psk_id=identity, psk=key) api = factory.request gateway = Gateway() diff --git a/homeassistant/helpers/aiohttp_client.py b/homeassistant/helpers/aiohttp_client.py index eee891b7f88..73658c7a6cb 100644 --- a/homeassistant/helpers/aiohttp_client.py +++ b/homeassistant/helpers/aiohttp_client.py @@ -64,10 +64,7 @@ def async_create_clientsession( connector = _async_get_connector(hass, verify_ssl) clientsession = aiohttp.ClientSession( - loop=hass.loop, - connector=connector, - headers={USER_AGENT: SERVER_SOFTWARE}, - **kwargs, + connector=connector, headers={USER_AGENT: SERVER_SOFTWARE}, **kwargs, ) if auto_cleanup: @@ -174,9 +171,7 @@ def _async_get_connector( else: ssl_context = False - connector = aiohttp.TCPConnector( - loop=hass.loop, enable_cleanup_closed=True, ssl=ssl_context - ) + connector = aiohttp.TCPConnector(enable_cleanup_closed=True, ssl=ssl_context) hass.data[key] = connector async def _async_close_connector(event: Event) -> None: From 03bb2a68b3ec42d3b5198ee07f8ab535defa394e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 30 Apr 2020 22:27:34 -0700 Subject: [PATCH 181/511] Lint roomba (#35000) --- homeassistant/components/roomba/irobot_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 86e9b0da6b5..6c35582c2c2 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -146,7 +146,7 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): state = STATE_MAP[phase] except KeyError: return STATE_ERROR - if cycle != "none" and (state == STATE_IDLE or state == STATE_DOCKED): + if cycle != "none" and state in (STATE_IDLE, STATE_DOCKED): state = STATE_PAUSED return state From 72e7beeb76e238fe5cf35f5b7a1d6f17425e5349 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 1 May 2020 07:33:22 +0200 Subject: [PATCH 182/511] UniFi - Add simple options flow (#34990) --- homeassistant/components/unifi/config_flow.py | 38 ++++++++++++++++++- homeassistant/components/unifi/strings.json | 8 ++++ .../components/unifi/translations/en.json | 8 ++++ tests/components/unifi/test_config_flow.py | 36 ++++++++++++++++-- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index f42acf54e9d..72ba593bae6 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -175,7 +175,43 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): """Manage the UniFi options.""" self.controller = self.hass.data[UNIFI_DOMAIN][self.config_entry.entry_id] self.options[CONF_BLOCK_CLIENT] = self.controller.option_block_clients - return await self.async_step_device_tracker() + + if self.show_advanced_options: + return await self.async_step_device_tracker() + + return await self.async_step_simple_options() + + async def async_step_simple_options(self, user_input=None): + """For simple Jack.""" + if user_input is not None: + self.options.update(user_input) + return await self._update_options() + + clients_to_block = {} + + for client in self.controller.api.clients.values(): + clients_to_block[ + client.mac + ] = f"{client.name or client.hostname} ({client.mac})" + + return self.async_show_form( + step_id="simple_options", + data_schema=vol.Schema( + { + vol.Optional( + CONF_TRACK_CLIENTS, + default=self.controller.option_track_clients, + ): bool, + vol.Optional( + CONF_TRACK_DEVICES, + default=self.controller.option_track_devices, + ): bool, + vol.Optional( + CONF_BLOCK_CLIENT, default=self.options[CONF_BLOCK_CLIENT] + ): cv.multi_select(clients_to_block), + } + ), + ) async def async_step_device_tracker(self, user_input=None): """Manage the device tracker options.""" diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index 6c142d371c9..da1d6200ed5 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -46,6 +46,14 @@ "description": "Configure client controls\n\nCreate switches for serial numbers you want to control network access for.", "title": "UniFi options 2/3" }, + "simple_options": { + "data": { + "track_clients": "[%key:component::unifi::options::step::device_tracker::data::track_clients%]", + "track_devices": "[%key:component::unifi::options::step::device_tracker::data::track_devices%]", + "block_client": "[%key:component::unifi::options::step::client_control::data::block_client%]" + }, + "description": "Configure UniFi integration" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients" diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 618c393b7aa..fd7096686e1 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -46,6 +46,14 @@ "description": "Configure device tracking", "title": "UniFi options 1/3" }, + "simple_options": { + "data": { + "track_clients": "[%key:component::unifi::options::step::device_tracker::data::track_clients%]", + "track_devices": "[%key:component::unifi::options::step::device_tracker::data::track_devices%]", + "block_client": "[%key:component::unifi::options::step::client_control::data::block_client%]" + }, + "description": "Configure UniFi integration" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients" diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index ae738ba8a64..489ae25a60c 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -264,14 +264,14 @@ async def test_flow_fails_unknown_problem(hass, aioclient_mock): assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT -async def test_option_flow(hass): - """Test config flow options.""" +async def test_advanced_option_flow(hass): + """Test advanced config flow options.""" controller = await setup_unifi_integration( hass, clients_response=CLIENTS, wlans_response=WLANS ) result = await hass.config_entries.options.async_init( - controller.config_entry.entry_id + controller.config_entry.entry_id, context={"show_advanced_options": True} ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -315,3 +315,33 @@ async def test_option_flow(hass): CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]], CONF_ALLOW_BANDWIDTH_SENSORS: True, } + + +async def test_simple_option_flow(hass): + """Test simple config flow options.""" + controller = await setup_unifi_integration( + hass, clients_response=CLIENTS, wlans_response=WLANS + ) + + result = await hass.config_entries.options.async_init( + controller.config_entry.entry_id, context={"show_advanced_options": False} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "simple_options" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]], + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == { + CONF_TRACK_CLIENTS: False, + CONF_TRACK_DEVICES: False, + CONF_BLOCK_CLIENT: [CLIENTS[0]["mac"]], + } From 82a478e2fb76803d8b704fd45b10f09d4348177a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 1 May 2020 07:34:44 +0200 Subject: [PATCH 183/511] Fix MQTT debug info for same topic (#34952) --- homeassistant/components/mqtt/debug_info.py | 25 ++++-- tests/components/mqtt/test_init.py | 93 ++++++++++++++++++++- 2 files changed, 109 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index 2a216366bb1..86850c61638 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -23,7 +23,7 @@ def log_messages(hass: HomeAssistantType, entity_id: str) -> MessageCallbackType debug_info = hass.data[DATA_MQTT_DEBUG_INFO] messages = debug_info["entities"][entity_id]["subscriptions"][ msg.subscribed_topic - ] + ]["messages"] if msg not in messages: messages.append(msg) @@ -50,16 +50,27 @@ def add_subscription(hass, message_callback, subscription): entity_info = debug_info["entities"].setdefault( entity_id, {"subscriptions": {}, "discovery_data": {}} ) - entity_info["subscriptions"][subscription] = deque([], STORED_MESSAGES) + if subscription not in entity_info["subscriptions"]: + entity_info["subscriptions"][subscription] = { + "count": 0, + "messages": deque([], STORED_MESSAGES), + } + entity_info["subscriptions"][subscription]["count"] += 1 def remove_subscription(hass, message_callback, subscription): - """Remove debug data for subscription.""" + """Remove debug data for subscription if it exists.""" entity_id = getattr(message_callback, "__entity_id", None) if entity_id and entity_id in hass.data[DATA_MQTT_DEBUG_INFO]["entities"]: - hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"].pop( + hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"][ subscription - ) + ]["count"] -= 1 + if not hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"][ + subscription + ]["count"]: + hass.data[DATA_MQTT_DEBUG_INFO]["entities"][entity_id]["subscriptions"].pop( + subscription + ) def add_entity_discovery_data(hass, discovery_data, entity_id): @@ -127,10 +138,10 @@ async def info_for_device(hass, device_id): "topic": topic, "messages": [ {"payload": msg.payload, "time": msg.timestamp, "topic": msg.topic} - for msg in list(messages) + for msg in list(subscription["messages"]) ], } - for topic, messages in entity_info["subscriptions"].items() + for topic, subscription in entity_info["subscriptions"].items() ] discovery_data = { "topic": entity_info["discovery_data"].get(ATTR_DISCOVERY_TOPIC, ""), diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index a139a942530..672ff127b4d 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -956,6 +956,42 @@ async def test_mqtt_ws_remove_discovered_device_twice( assert response["error"]["code"] == websocket_api.const.ERR_NOT_FOUND +async def test_mqtt_ws_remove_discovered_device_same_topic( + hass, device_reg, hass_ws_client, mqtt_mock +): + """Test MQTT websocket device removal.""" + config_entry = MockConfigEntry(domain=mqtt.DOMAIN) + config_entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, config_entry) + + data = ( + '{ "device":{"identifiers":["0AFFD2"]},' + ' "state_topic": "foobar/sensor",' + ' "availability_topic": "foobar/sensor",' + ' "unique_id": "unique" }' + ) + + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set()) + assert device_entry is not None + + client = await hass_ws_client(hass) + await client.send_json( + {"id": 5, "type": "mqtt/device/remove", "device_id": device_entry.id} + ) + response = await client.receive_json() + assert response["success"] + + await client.send_json( + {"id": 6, "type": "mqtt/device/remove", "device_id": device_entry.id} + ) + response = await client.receive_json() + assert not response["success"] + assert response["error"]["code"] == websocket_api.const.ERR_NOT_FOUND + + async def test_mqtt_ws_remove_non_mqtt_device( hass, device_reg, hass_ws_client, mqtt_mock ): @@ -1302,7 +1338,60 @@ async def test_debug_info_filter_same(hass, mqtt_mock): assert { "topic": "sensor/#", "messages": [ - {"topic": "sensor/abc", "payload": "123", "time": dt1}, - {"topic": "sensor/abc", "payload": "123", "time": dt2}, + {"payload": "123", "time": dt1, "topic": "sensor/abc"}, + {"payload": "123", "time": dt2, "topic": "sensor/abc"}, ], } == debug_info_data["entities"][0]["subscriptions"][0] + + +async def test_debug_info_same_topic(hass, mqtt_mock): + """Test debug info.""" + config = { + "device": {"identifiers": ["helloworld"]}, + "platform": "mqtt", + "name": "test", + "state_topic": "sensor/status", + "availability_topic": "sensor/status", + "unique_id": "veryunique", + } + + entry = MockConfigEntry(domain=mqtt.DOMAIN) + entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, entry) + registry = await hass.helpers.device_registry.async_get_registry() + + data = json.dumps(config) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}, set()) + assert device is not None + + debug_info_data = await debug_info.info_for_device(hass, device.id) + assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 + assert {"topic": "sensor/status", "messages": []} in debug_info_data["entities"][0][ + "subscriptions" + ] + + start_dt = datetime(2019, 1, 1, 0, 0, 0) + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: + dt_utcnow.return_value = start_dt + async_fire_mqtt_message(hass, "sensor/status", "123", qos=0, retain=False) + + debug_info_data = await debug_info.info_for_device(hass, device.id) + assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 + assert { + "payload": "123", + "time": start_dt, + "topic": "sensor/status", + } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] + + config["availability_topic"] = "sensor/availability" + data = json.dumps(config) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + start_dt = datetime(2019, 1, 1, 0, 0, 0) + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: + dt_utcnow.return_value = start_dt + async_fire_mqtt_message(hass, "sensor/status", "123", qos=0, retain=False) From af62660b14a807b773b5ecfc4192523ba98a25ce Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 May 2020 00:35:23 -0500 Subject: [PATCH 184/511] Attempt to fix flapping august lock test (#34998) --- tests/components/august/test_lock.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index 4bd5509a216..dfa0edfcb6d 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -71,6 +71,7 @@ async def test_one_lock_operation(hass): assert await hass.services.async_call( LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True ) + 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 @@ -84,6 +85,7 @@ async def test_one_lock_operation(hass): assert await hass.services.async_call( LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True ) + 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 From e7157f216409a6536661e091e8d29b39b1bfa72c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 May 2020 00:36:01 -0500 Subject: [PATCH 185/511] Fix restoring isy994 brightness with no previous state (#34972) --- CODEOWNERS | 1 + homeassistant/components/isy994/light.py | 29 +++++++++++++++++-- homeassistant/components/isy994/manifest.json | 2 +- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 43959f67c30..4dbd9f87d4a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -196,6 +196,7 @@ homeassistant/components/ipp/* @ctalkington homeassistant/components/iqvia/* @bachya homeassistant/components/irish_rail_transport/* @ttroy50 homeassistant/components/islamic_prayer_times/* @engrbm87 +homeassistant/components/isy994/* @bdraco homeassistant/components/izone/* @Swamp-Ig homeassistant/components/jewish_calendar/* @tsvi homeassistant/components/juicenet/* @jesserockz diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 0d66a73571d..7ae8d1c76f8 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -3,12 +3,15 @@ import logging from typing import Callable from homeassistant.components.light import DOMAIN, SUPPORT_BRIGHTNESS, LightEntity +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType from . import ISY994_NODES, ISYDevice _LOGGER = logging.getLogger(__name__) +ATTR_LAST_BRIGHTNESS = "last_brightness" + def setup_platform( hass, config: ConfigType, add_entities: Callable[[list], None], discovery_info=None @@ -21,13 +24,13 @@ def setup_platform( add_entities(devices) -class ISYLightDevice(ISYDevice, LightEntity): +class ISYLightDevice(ISYDevice, LightEntity, RestoreEntity): """Representation of an ISY994 light device.""" def __init__(self, node) -> None: """Initialize the ISY994 light device.""" super().__init__(node) - self._last_brightness = self.brightness + self._last_brightness = None @property def is_on(self) -> bool: @@ -56,7 +59,7 @@ class ISYLightDevice(ISYDevice, LightEntity): # pylint: disable=arguments-differ def turn_on(self, brightness=None, **kwargs) -> None: """Send the turn on command to the ISY994 light device.""" - if brightness is None and self._last_brightness is not None: + if brightness is None and self._last_brightness: brightness = self._last_brightness if not self._node.on(val=brightness): _LOGGER.debug("Unable to turn on light") @@ -65,3 +68,23 @@ class ISYLightDevice(ISYDevice, LightEntity): def supported_features(self): """Flag supported features.""" return SUPPORT_BRIGHTNESS + + @property + def device_state_attributes(self): + """Return the light attributes.""" + return {ATTR_LAST_BRIGHTNESS: self._last_brightness} + + async def async_added_to_hass(self) -> None: + """Restore last_brightness on restart.""" + await super().async_added_to_hass() + + self._last_brightness = self.brightness or 255 + last_state = await self.async_get_last_state() + if not last_state: + return + + if ( + ATTR_LAST_BRIGHTNESS in last_state.attributes + and last_state.attributes[ATTR_LAST_BRIGHTNESS] + ): + self._last_brightness = last_state.attributes[ATTR_LAST_BRIGHTNESS] diff --git a/homeassistant/components/isy994/manifest.json b/homeassistant/components/isy994/manifest.json index 0b48528335d..083f25808fb 100644 --- a/homeassistant/components/isy994/manifest.json +++ b/homeassistant/components/isy994/manifest.json @@ -3,5 +3,5 @@ "name": "Universal Devices ISY994", "documentation": "https://www.home-assistant.io/integrations/isy994", "requirements": ["PyISY==1.1.2"], - "codeowners": [] + "codeowners": ["@bdraco"] } From 850b5cb02b2ed00eeb2b9807f2bdf6499bc5e781 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 1 May 2020 02:15:40 -0400 Subject: [PATCH 186/511] Config flow for ONVIF (#34520) --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/onvif/__init__.py | 87 ++- homeassistant/components/onvif/camera.py | 467 +++++++--------- homeassistant/components/onvif/config_flow.py | 311 +++++++++++ homeassistant/components/onvif/const.py | 47 ++ homeassistant/components/onvif/manifest.json | 5 +- homeassistant/components/onvif/strings.json | 58 ++ .../components/onvif/translations/en.json | 59 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 6 + tests/components/onvif/__init__.py | 1 + tests/components/onvif/test_config_flow.py | 508 ++++++++++++++++++ 14 files changed, 1292 insertions(+), 263 deletions(-) create mode 100644 homeassistant/components/onvif/config_flow.py create mode 100644 homeassistant/components/onvif/const.py create mode 100644 homeassistant/components/onvif/strings.json create mode 100644 homeassistant/components/onvif/translations/en.json create mode 100644 tests/components/onvif/__init__.py create mode 100644 tests/components/onvif/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 59ef37cd932..e36d6b252bd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -507,6 +507,7 @@ omit = homeassistant/components/ombi/* homeassistant/components/onewire/sensor.py homeassistant/components/onkyo/media_player.py + homeassistant/components/onvif/__init__.py homeassistant/components/onvif/camera.py homeassistant/components/opencv/* homeassistant/components/openevse/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 4dbd9f87d4a..a021a4a28ed 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -277,6 +277,7 @@ homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ombi/* @larssont homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/onewire/* @garbled1 +homeassistant/components/onvif/* @hunterjm homeassistant/components/openerz/* @misialq homeassistant/components/opentherm_gw/* @mvn23 homeassistant/components/openuv/* @bachya diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index ea4c875ac20..5fe43fdd83b 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -1 +1,86 @@ -"""The onvif component.""" +"""The ONVIF integration.""" +import asyncio + +import voluptuous as vol + +from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.helpers import config_per_platform + +from .const import ( + CONF_PROFILE, + CONF_RTSP_TRANSPORT, + DEFAULT_ARGUMENTS, + DEFAULT_PROFILE, + DOMAIN, + RTSP_TRANS_PROTOCOLS, +) + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + +PLATFORMS = ["camera"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the ONVIF component.""" + # Import from yaml + configs = {} + for p_type, p_config in config_per_platform(config, "camera"): + if p_type != DOMAIN: + continue + + config = p_config.copy() + profile = config.get(CONF_PROFILE, DEFAULT_PROFILE) + if config[CONF_HOST] not in configs.keys(): + configs[config[CONF_HOST]] = config + configs[config[CONF_HOST]][CONF_PROFILE] = [profile] + else: + configs[config[CONF_HOST]][CONF_PROFILE].append(profile) + + for conf in configs.values(): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up ONVIF from a config entry.""" + if not entry.options: + await async_populate_options(hass, entry) + + 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 + ] + ) + ) + + return unload_ok + + +async def async_populate_options(hass, entry): + """Populate default options for device.""" + options = { + CONF_EXTRA_ARGUMENTS: DEFAULT_ARGUMENTS, + CONF_RTSP_TRANSPORT: RTSP_TRANS_PROTOCOLS[0], + } + + hass.config_entries.async_update_entry(entry, options=options) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 7fea75735e0..34f33e302b8 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -1,7 +1,6 @@ """Support for ONVIF Cameras with FFmpeg as decoder.""" import asyncio import datetime as dt -import logging import os from typing import Optional @@ -16,10 +15,9 @@ import voluptuous as vol from zeep.asyncio import AsyncTransport from zeep.exceptions import Fault -from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera +from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS, DATA_FFMPEG from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PASSWORD, @@ -27,131 +25,85 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.exceptions import PlatformNotReady +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_stream, async_get_clientsession, ) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.service import async_extract_entity_ids import homeassistant.util.dt as dt_util -_LOGGER = logging.getLogger(__name__) - -DEFAULT_NAME = "ONVIF Camera" -DEFAULT_PORT = 5000 -DEFAULT_USERNAME = "admin" -DEFAULT_PASSWORD = "888888" -DEFAULT_ARGUMENTS = "-pred 1" -DEFAULT_PROFILE = 0 - -CONF_PROFILE = "profile" -CONF_RTSP_TRANSPORT = "rtsp_transport" - -ATTR_PAN = "pan" -ATTR_TILT = "tilt" -ATTR_ZOOM = "zoom" -ATTR_DISTANCE = "distance" -ATTR_SPEED = "speed" -ATTR_MOVE_MODE = "move_mode" -ATTR_CONTINUOUS_DURATION = "continuous_duration" -ATTR_PRESET = "preset" - -DIR_UP = "UP" -DIR_DOWN = "DOWN" -DIR_LEFT = "LEFT" -DIR_RIGHT = "RIGHT" -ZOOM_OUT = "ZOOM_OUT" -ZOOM_IN = "ZOOM_IN" -PAN_FACTOR = {DIR_RIGHT: 1, DIR_LEFT: -1} -TILT_FACTOR = {DIR_UP: 1, DIR_DOWN: -1} -ZOOM_FACTOR = {ZOOM_IN: 1, ZOOM_OUT: -1} -CONTINUOUS_MOVE = "ContinuousMove" -RELATIVE_MOVE = "RelativeMove" -ABSOLUTE_MOVE = "AbsoluteMove" -GOTOPRESET_MOVE = "GotoPreset" - -SERVICE_PTZ = "ptz" - -DOMAIN = "onvif" -ONVIF_DATA = "onvif" -ENTITIES = "entities" - -RTSP_TRANS_PROTOCOLS = ["tcp", "udp", "udp_multicast", "http"] - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, - vol.Optional(CONF_RTSP_TRANSPORT, default=RTSP_TRANS_PROTOCOLS[0]): vol.In( - RTSP_TRANS_PROTOCOLS - ), - vol.Optional(CONF_PROFILE, default=DEFAULT_PROFILE): vol.All( - vol.Coerce(int), vol.Range(min=0) - ), - } -) - -SERVICE_PTZ_SCHEMA = vol.Schema( - { - ATTR_ENTITY_ID: cv.entity_ids, - vol.Optional(ATTR_PAN): vol.In([DIR_LEFT, DIR_RIGHT]), - vol.Optional(ATTR_TILT): vol.In([DIR_UP, DIR_DOWN]), - vol.Optional(ATTR_ZOOM): vol.In([ZOOM_OUT, ZOOM_IN]), - ATTR_MOVE_MODE: vol.In( - [CONTINUOUS_MOVE, RELATIVE_MOVE, ABSOLUTE_MOVE, GOTOPRESET_MOVE] - ), - vol.Optional(ATTR_CONTINUOUS_DURATION, default=0.5): cv.small_float, - vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float, - vol.Optional(ATTR_SPEED, default=0.5): cv.small_float, - vol.Optional(ATTR_PRESET, default="0"): cv.string, - } +from .const import ( + ABSOLUTE_MOVE, + ATTR_CONTINUOUS_DURATION, + ATTR_DISTANCE, + ATTR_MOVE_MODE, + ATTR_PAN, + ATTR_PRESET, + ATTR_SPEED, + ATTR_TILT, + ATTR_ZOOM, + CONF_PROFILE, + CONF_RTSP_TRANSPORT, + CONTINUOUS_MOVE, + DIR_DOWN, + DIR_LEFT, + DIR_RIGHT, + DIR_UP, + DOMAIN, + ENTITIES, + GOTOPRESET_MOVE, + LOGGER, + PAN_FACTOR, + RELATIVE_MOVE, + SERVICE_PTZ, + TILT_FACTOR, + ZOOM_FACTOR, + ZOOM_IN, + ZOOM_OUT, ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up a ONVIF camera.""" - _LOGGER.debug("Setting up the ONVIF camera platform") +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the ONVIF camera video stream.""" + platform = entity_platform.current_platform.get() - async def async_handle_ptz(service): - """Handle PTZ service call.""" - pan = service.data.get(ATTR_PAN) - tilt = service.data.get(ATTR_TILT) - zoom = service.data.get(ATTR_ZOOM) - distance = service.data[ATTR_DISTANCE] - speed = service.data[ATTR_SPEED] - move_mode = service.data.get(ATTR_MOVE_MODE) - continuous_duration = service.data[ATTR_CONTINUOUS_DURATION] - preset = service.data[ATTR_PRESET] - all_cameras = hass.data[ONVIF_DATA][ENTITIES] - entity_ids = await async_extract_entity_ids(hass, service) - target_cameras = [] - if not entity_ids: - target_cameras = all_cameras - else: - target_cameras = [ - camera for camera in all_cameras if camera.entity_id in entity_ids - ] - for camera in target_cameras: - await camera.async_perform_ptz( - pan, tilt, zoom, distance, speed, move_mode, continuous_duration, preset - ) - - hass.services.async_register( - DOMAIN, SERVICE_PTZ, async_handle_ptz, schema=SERVICE_PTZ_SCHEMA + # Create PTZ service + platform.async_register_entity_service( + SERVICE_PTZ, + { + vol.Optional(ATTR_PAN): vol.In([DIR_LEFT, DIR_RIGHT]), + vol.Optional(ATTR_TILT): vol.In([DIR_UP, DIR_DOWN]), + vol.Optional(ATTR_ZOOM): vol.In([ZOOM_OUT, ZOOM_IN]), + vol.Optional(ATTR_DISTANCE, default=0.1): cv.small_float, + vol.Optional(ATTR_SPEED, default=0.5): cv.small_float, + vol.Optional(ATTR_MOVE_MODE, default=RELATIVE_MOVE): vol.In( + [CONTINUOUS_MOVE, RELATIVE_MOVE, ABSOLUTE_MOVE, GOTOPRESET_MOVE] + ), + vol.Optional(ATTR_CONTINUOUS_DURATION, default=0.5): cv.small_float, + vol.Optional(ATTR_PRESET, default="0"): cv.string, + }, + "async_perform_ptz", ) - _LOGGER.debug("Constructing the ONVIFHassCamera") + base_config = { + CONF_NAME: config_entry.data[CONF_NAME], + CONF_HOST: config_entry.data[CONF_HOST], + CONF_PORT: config_entry.data[CONF_PORT], + CONF_USERNAME: config_entry.data[CONF_USERNAME], + CONF_PASSWORD: config_entry.data[CONF_PASSWORD], + CONF_EXTRA_ARGUMENTS: config_entry.options[CONF_EXTRA_ARGUMENTS], + CONF_RTSP_TRANSPORT: config_entry.options[CONF_RTSP_TRANSPORT], + } - hass_camera = ONVIFHassCamera(hass, config) + entities = [] + for profile in config_entry.data[CONF_PROFILE]: + config = {**base_config, CONF_PROFILE: profile} + camera = ONVIFHassCamera(hass, config) + await camera.async_initialize() + entities.append(camera) - await hass_camera.async_initialize() - - async_add_entities([hass_camera]) - return + async_add_entities(entities) + return True class ONVIFHassCamera(Camera): @@ -161,9 +113,9 @@ class ONVIFHassCamera(Camera): """Initialize an ONVIF camera.""" super().__init__() - _LOGGER.debug("Importing dependencies") + LOGGER.debug("Importing dependencies") - _LOGGER.debug("Setting up the ONVIF camera component") + LOGGER.debug("Setting up the ONVIF camera component") self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) @@ -172,13 +124,18 @@ class ONVIFHassCamera(Camera): self._name = config.get(CONF_NAME) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) + self._profile_token = None + self._profile_name = None self._ptz_service = None self._input = None self._snapshot = None self.stream_options[CONF_RTSP_TRANSPORT] = config.get(CONF_RTSP_TRANSPORT) + self._manufacturer = None + self._model = None + self._firmware_version = None self._mac = None - _LOGGER.debug( + LOGGER.debug( "Setting up the ONVIF camera device @ '%s:%s'", self._host, self._port ) @@ -201,29 +158,39 @@ class ONVIFHassCamera(Camera): the camera. Also retrieves the ONVIF profiles. """ try: - _LOGGER.debug("Updating service addresses") + LOGGER.debug("Updating service addresses") await self._camera.update_xaddrs() + await self.async_obtain_device_info() await self.async_obtain_mac_address() await self.async_check_date_and_time() + await self.async_obtain_profile_token() await self.async_obtain_input_uri() await self.async_obtain_snapshot_uri() self.setup_ptz() except ClientConnectionError as err: - _LOGGER.warning( + LOGGER.warning( "Couldn't connect to camera '%s', but will retry later. Error: %s", self._name, err, ) raise PlatformNotReady except Fault as err: - _LOGGER.error( + LOGGER.error( "Couldn't connect to camera '%s', please verify " "that the credentials are correct. Error: %s", self._name, err, ) + async def async_obtain_device_info(self): + """Obtain the MAC address of the camera to use as the unique ID.""" + devicemgmt = self._camera.create_devicemgmt_service() + device_info = await devicemgmt.GetDeviceInformation() + self._manufacturer = device_info.Manufacturer + self._model = device_info.Model + self._firmware_version = device_info.FirmwareVersion + async def async_obtain_mac_address(self): """Obtain the MAC address of the camera to use as the unique ID.""" devicemgmt = self._camera.create_devicemgmt_service() @@ -234,15 +201,15 @@ class ONVIFHassCamera(Camera): async def async_check_date_and_time(self): """Warns if camera and system date not synced.""" - _LOGGER.debug("Setting up the ONVIF device management service") + LOGGER.debug("Setting up the ONVIF device management service") devicemgmt = self._camera.create_devicemgmt_service() - _LOGGER.debug("Retrieving current camera date/time") + LOGGER.debug("Retrieving current camera date/time") try: system_date = dt_util.utcnow() device_time = await devicemgmt.GetSystemDateAndTime() if not device_time: - _LOGGER.debug( + LOGGER.debug( """Couldn't get camera '%s' date/time. GetSystemDateAndTime() return null/empty""", self._name, @@ -260,7 +227,7 @@ class ONVIFHassCamera(Camera): cdate = device_time.LocalDateTime if cdate is None: - _LOGGER.warning("Could not retrieve date/time on this camera") + LOGGER.warning("Could not retrieve date/time on this camera") else: cam_date = dt.datetime( cdate.Date.Year, @@ -275,19 +242,19 @@ class ONVIFHassCamera(Camera): cam_date_utc = cam_date.astimezone(dt_util.UTC) - _LOGGER.debug("TimeZone for date/time: %s", tzone) + LOGGER.debug("TimeZone for date/time: %s", tzone) - _LOGGER.debug("Camera date/time: %s", cam_date) + LOGGER.debug("Camera date/time: %s", cam_date) - _LOGGER.debug("Camera date/time in UTC: %s", cam_date_utc) + LOGGER.debug("Camera date/time in UTC: %s", cam_date_utc) - _LOGGER.debug("System date/time: %s", system_date) + LOGGER.debug("System date/time: %s", system_date) dt_diff = cam_date - system_date dt_diff_seconds = dt_diff.total_seconds() if dt_diff_seconds > 5: - _LOGGER.warning( + LOGGER.warning( "The date/time on the camera (UTC) is '%s', " "which is different from the system '%s', " "this could lead to authentication issues", @@ -295,7 +262,7 @@ class ONVIFHassCamera(Camera): system_date, ) except ServerDisconnectedError as err: - _LOGGER.warning( + LOGGER.warning( "Couldn't get camera '%s' date/time. Error: %s", self._name, err ) @@ -306,10 +273,10 @@ class ONVIFHassCamera(Camera): profiles = await media_service.GetProfiles() - _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) + LOGGER.debug("Retrieved '%d' profiles", len(profiles)) if self._profile_index >= len(profiles): - _LOGGER.warning( + LOGGER.warning( "ONVIF Camera '%s' doesn't provide profile %d." " Using the last profile.", self._name, @@ -317,51 +284,32 @@ class ONVIFHassCamera(Camera): ) self._profile_index = -1 - _LOGGER.debug("Using profile index '%d'", self._profile_index) + LOGGER.debug("Using profile index '%d'", self._profile_index) - return profiles[self._profile_index].token + self._profile_token = profiles[self._profile_index].token + self._profile_name = profiles[self._profile_index].Name except exceptions.ONVIFError as err: - _LOGGER.error( + LOGGER.error( "Couldn't retrieve profile token of camera '%s'. Error: %s", self._name, err, ) - return None async def async_obtain_input_uri(self): """Set the input uri for the camera.""" - _LOGGER.debug( + LOGGER.debug( "Connecting with ONVIF Camera: %s on port %s", self._host, self._port ) try: - _LOGGER.debug("Retrieving profiles") - - media_service = self._camera.create_media_service() - - profiles = await media_service.GetProfiles() - - _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) - - if self._profile_index >= len(profiles): - _LOGGER.warning( - "ONVIF Camera '%s' doesn't provide profile %d." - " Using the last profile.", - self._name, - self._profile_index, - ) - self._profile_index = -1 - - _LOGGER.debug("Using profile index '%d'", self._profile_index) - - _LOGGER.debug("Retrieving stream uri") + LOGGER.debug("Retrieving stream uri") # Fix Onvif setup error on Goke GK7102 based IP camera # where we need to recreate media_service #26781 media_service = self._camera.create_media_service() req = media_service.create_type("GetStreamUri") - req.ProfileToken = profiles[self._profile_index].token + req.ProfileToken = self._profile_token req.StreamSetup = { "Stream": "RTP-Unicast", "Transport": {"Protocol": "RTSP"}, @@ -374,155 +322,143 @@ class ONVIFHassCamera(Camera): "rtsp://", f"rtsp://{self._username}:{self._password}@", 1 ) - _LOGGER.debug( + LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", self._name, uri_for_log, ) except exceptions.ONVIFError as err: - _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) + LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) async def async_obtain_snapshot_uri(self): """Set the snapshot uri for the camera.""" - _LOGGER.debug( + LOGGER.debug( "Connecting with ONVIF Camera: %s on port %s", self._host, self._port ) try: - _LOGGER.debug("Retrieving profiles") - - media_service = self._camera.create_media_service() - - profiles = await media_service.GetProfiles() - - _LOGGER.debug("Retrieved '%d' profiles", len(profiles)) - - if self._profile_index >= len(profiles): - _LOGGER.warning( - "ONVIF Camera '%s' doesn't provide profile %d." - " Using the last profile.", - self._name, - self._profile_index, - ) - self._profile_index = -1 - - _LOGGER.debug("Using profile index '%d'", self._profile_index) - - _LOGGER.debug("Retrieving snapshot uri") + LOGGER.debug("Retrieving snapshot uri") # Fix Onvif setup error on Goke GK7102 based IP camera # where we need to recreate media_service #26781 media_service = self._camera.create_media_service() req = media_service.create_type("GetSnapshotUri") - req.ProfileToken = profiles[self._profile_index].token + req.ProfileToken = self._profile_token try: snapshot_uri = await media_service.GetSnapshotUri(req) self._snapshot = snapshot_uri.Uri except ServerDisconnectedError as err: - _LOGGER.debug("Camera does not support GetSnapshotUri: %s", err) + LOGGER.debug("Camera does not support GetSnapshotUri: %s", err) - _LOGGER.debug( + LOGGER.debug( "ONVIF Camera Using the following URL for %s snapshot: %s", self._name, self._snapshot, ) except exceptions.ONVIFError as err: - _LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) + LOGGER.error("Couldn't setup camera '%s'. Error: %s", self._name, err) def setup_ptz(self): """Set up PTZ if available.""" - _LOGGER.debug("Setting up the ONVIF PTZ service") + LOGGER.debug("Setting up the ONVIF PTZ service") if self._camera.get_service("ptz", create=False) is None: - _LOGGER.debug("PTZ is not available") + LOGGER.debug("PTZ is not available") else: self._ptz_service = self._camera.create_ptz_service() - _LOGGER.debug("Completed set up of the ONVIF camera component") + LOGGER.debug("Completed set up of the ONVIF camera component") async def async_perform_ptz( - self, pan, tilt, zoom, distance, speed, move_mode, continuous_duration, preset + self, + distance, + speed, + move_mode, + continuous_duration, + preset, + pan=None, + tilt=None, + zoom=None, ): """Perform a PTZ action on the camera.""" if self._ptz_service is None: - _LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) + LOGGER.warning("PTZ actions are not supported on camera '%s'", self._name) return - if self._ptz_service: - pan_val = distance * PAN_FACTOR.get(pan, 0) - tilt_val = distance * TILT_FACTOR.get(tilt, 0) - zoom_val = distance * ZOOM_FACTOR.get(zoom, 0) - speed_val = speed - preset_val = preset - _LOGGER.debug( - "Calling %s PTZ | Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %4.2f | Preset = %s", - move_mode, - pan_val, - tilt_val, - zoom_val, - speed_val, - preset_val, - ) - try: - req = self._ptz_service.create_type(move_mode) - req.ProfileToken = await self.async_obtain_profile_token() - if move_mode == CONTINUOUS_MOVE: - req.Velocity = { - "PanTilt": {"x": pan_val, "y": tilt_val}, - "Zoom": {"x": zoom_val}, - } + pan_val = distance * PAN_FACTOR.get(pan, 0) + tilt_val = distance * TILT_FACTOR.get(tilt, 0) + zoom_val = distance * ZOOM_FACTOR.get(zoom, 0) + speed_val = speed + preset_val = preset + LOGGER.debug( + "Calling %s PTZ | Pan = %4.2f | Tilt = %4.2f | Zoom = %4.2f | Speed = %4.2f | Preset = %s", + move_mode, + pan_val, + tilt_val, + zoom_val, + speed_val, + preset_val, + ) + try: + req = self._ptz_service.create_type(move_mode) + req.ProfileToken = self._profile_token + if move_mode == CONTINUOUS_MOVE: + req.Velocity = { + "PanTilt": {"x": pan_val, "y": tilt_val}, + "Zoom": {"x": zoom_val}, + } - await self._ptz_service.ContinuousMove(req) - await asyncio.sleep(continuous_duration) - req = self._ptz_service.create_type("Stop") - req.ProfileToken = await self.async_obtain_profile_token() - await self._ptz_service.Stop({"ProfileToken": req.ProfileToken}) - elif move_mode == RELATIVE_MOVE: - req.Translation = { - "PanTilt": {"x": pan_val, "y": tilt_val}, - "Zoom": {"x": zoom_val}, - } - req.Speed = { - "PanTilt": {"x": speed_val, "y": speed_val}, - "Zoom": {"x": speed_val}, - } - await self._ptz_service.RelativeMove(req) - elif move_mode == ABSOLUTE_MOVE: - req.Position = { - "PanTilt": {"x": pan_val, "y": tilt_val}, - "Zoom": {"x": zoom_val}, - } - req.Speed = { - "PanTilt": {"x": speed_val, "y": speed_val}, - "Zoom": {"x": speed_val}, - } - await self._ptz_service.AbsoluteMove(req) - elif move_mode == GOTOPRESET_MOVE: - req.PresetToken = preset_val - req.Speed = { - "PanTilt": {"x": speed_val, "y": speed_val}, - "Zoom": {"x": speed_val}, - } - await self._ptz_service.GotoPreset(req) - except exceptions.ONVIFError as err: - if "Bad Request" in err.reason: - self._ptz_service = None - _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) - else: - _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) + await self._ptz_service.ContinuousMove(req) + await asyncio.sleep(continuous_duration) + req = self._ptz_service.create_type("Stop") + req.ProfileToken = self._profile_token + await self._ptz_service.Stop({"ProfileToken": req.ProfileToken}) + elif move_mode == RELATIVE_MOVE: + req.Translation = { + "PanTilt": {"x": pan_val, "y": tilt_val}, + "Zoom": {"x": zoom_val}, + } + req.Speed = { + "PanTilt": {"x": speed_val, "y": speed_val}, + "Zoom": {"x": speed_val}, + } + await self._ptz_service.RelativeMove(req) + elif move_mode == ABSOLUTE_MOVE: + req.Position = { + "PanTilt": {"x": pan_val, "y": tilt_val}, + "Zoom": {"x": zoom_val}, + } + req.Speed = { + "PanTilt": {"x": speed_val, "y": speed_val}, + "Zoom": {"x": speed_val}, + } + await self._ptz_service.AbsoluteMove(req) + elif move_mode == GOTOPRESET_MOVE: + req.PresetToken = preset_val + req.Speed = { + "PanTilt": {"x": speed_val, "y": speed_val}, + "Zoom": {"x": speed_val}, + } + await self._ptz_service.GotoPreset(req) + except exceptions.ONVIFError as err: + if "Bad Request" in err.reason: + self._ptz_service = None + LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) + else: + LOGGER.error("Error trying to perform PTZ action: %s", err) async def async_added_to_hass(self): """Handle entity addition to hass.""" - _LOGGER.debug("Camera '%s' added to hass", self._name) + LOGGER.debug("Camera '%s' added to hass", self._name) - if ONVIF_DATA not in self.hass.data: - self.hass.data[ONVIF_DATA] = {} - self.hass.data[ONVIF_DATA][ENTITIES] = [] - self.hass.data[ONVIF_DATA][ENTITIES].append(self) + if DOMAIN not in self.hass.data: + self.hass.data[DOMAIN] = {} + self.hass.data[DOMAIN][ENTITIES] = [] + self.hass.data[DOMAIN][ENTITIES].append(self) async def async_camera_image(self): """Return a still image response from the camera.""" - _LOGGER.debug("Retrieving image from camera '%s'", self._name) + LOGGER.debug("Retrieving image from camera '%s'", self._name) image = None if self._snapshot is not None: @@ -537,7 +473,7 @@ class ONVIFHassCamera(Camera): if response.status_code < 300: return response.content except requests.exceptions.RequestException as error: - _LOGGER.error( + LOGGER.error( "Fetch snapshot image failed from %s, falling back to FFmpeg; %s", self._name, error, @@ -564,7 +500,7 @@ class ONVIFHassCamera(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" - _LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) + LOGGER.debug("Handling mjpeg stream from camera '%s'", self._name) ffmpeg_manager = self.hass.data[DATA_FFMPEG] stream = CameraMjpeg(ffmpeg_manager.binary, loop=self.hass.loop) @@ -596,7 +532,7 @@ class ONVIFHassCamera(Camera): @property def name(self): """Return the name of this camera.""" - return self._name + return f"{self._name} - {self._profile_name}" @property def unique_id(self) -> Optional[str]: @@ -604,3 +540,14 @@ class ONVIFHassCamera(Camera): if self._profile_index: return f"{self._mac}_{self._profile_index}" return self._mac + + @property + def device_info(self): + """Return a device description for device registry.""" + return { + "identifiers": {(DOMAIN, self._mac)}, + "name": self._name, + "manufacturer": self._manufacturer, + "model": self._model, + "sw_version": self._firmware_version, + } diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py new file mode 100644 index 00000000000..d5b153e2747 --- /dev/null +++ b/homeassistant/components/onvif/config_flow.py @@ -0,0 +1,311 @@ +"""Config flow for ONVIF.""" +import os +from pprint import pformat +from typing import List +from urllib.parse import urlparse + +import onvif +from onvif import ONVIFCamera, exceptions +import voluptuous as vol +from wsdiscovery.discovery import ThreadedWSDiscovery as WSDiscovery +from wsdiscovery.scope import Scope +from wsdiscovery.service import Service +from zeep.asyncio import AsyncTransport +from zeep.exceptions import Fault + +from homeassistant import config_entries +from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) +from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +# pylint: disable=unused-import +from .const import ( + CONF_DEVICE_ID, + CONF_PROFILE, + CONF_RTSP_TRANSPORT, + DEFAULT_ARGUMENTS, + DEFAULT_PORT, + DOMAIN, + LOGGER, + RTSP_TRANS_PROTOCOLS, +) + +CONF_MANUAL_INPUT = "Manually configure ONVIF device" + + +def wsdiscovery() -> List[Service]: + """Get ONVIF Profile S devices from network.""" + discovery = WSDiscovery(ttl=4) + discovery.start() + services = discovery.searchServices( + scopes=[Scope("onvif://www.onvif.org/Profile/Streaming")] + ) + discovery.stop() + return services + + +async def async_discovery(hass) -> bool: + """Return if there are devices that can be discovered.""" + LOGGER.debug("Starting ONVIF discovery...") + services = await hass.async_add_executor_job(wsdiscovery) + + devices = [] + for service in services: + url = urlparse(service.getXAddrs()[0]) + device = { + CONF_DEVICE_ID: None, + CONF_NAME: service.getEPR(), + CONF_HOST: url.hostname, + CONF_PORT: url.port or 80, + } + for scope in service.getScopes(): + scope_str = scope.getValue() + if scope_str.lower().startswith("onvif://www.onvif.org/name"): + device[CONF_NAME] = scope_str.split("/")[-1] + if scope_str.lower().startswith("onvif://www.onvif.org/mac"): + device[CONF_DEVICE_ID] = scope_str.split("/")[-1] + devices.append(device) + + return devices + + +class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a ONVIF config flow.""" + + 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 OnvifOptionsFlowHandler(config_entry) + + def __init__(self): + """Initialize the ONVIF config flow.""" + self.device_id = None + self.devices = [] + self.onvif_config = {} + + async def async_step_user(self, user_input=None): + """Handle user flow.""" + if user_input is not None: + return await self.async_step_device() + + return self.async_show_form(step_id="user") + + async def async_step_device(self, user_input=None): + """Handle WS-Discovery. + + Let user choose between discovered devices and manual configuration. + If no device is found allow user to manually input configuration. + """ + if user_input: + + if CONF_MANUAL_INPUT == user_input[CONF_HOST]: + return await self.async_step_manual_input() + + for device in self.devices: + name = f"{device[CONF_NAME]} ({device[CONF_HOST]})" + if name == user_input[CONF_HOST]: + self.device_id = device[CONF_DEVICE_ID] + self.onvif_config = { + CONF_NAME: device[CONF_NAME], + CONF_HOST: device[CONF_HOST], + CONF_PORT: device[CONF_PORT], + } + return await self.async_step_auth() + + discovery = await async_discovery(self.hass) + for device in discovery: + configured = False + for entry in self._async_current_entries(): + if entry.unique_id == device[CONF_DEVICE_ID]: + configured = True + break + if not configured: + self.devices.append(device) + + LOGGER.debug("Discovered ONVIF devices %s", pformat(self.devices)) + + if self.devices: + names = [] + + for device in self.devices: + names.append(f"{device[CONF_NAME]} ({device[CONF_HOST]})") + + names.append(CONF_MANUAL_INPUT) + + return self.async_show_form( + step_id="device", + data_schema=vol.Schema({vol.Optional(CONF_HOST): vol.In(names)}), + ) + + return await self.async_step_manual_input() + + async def async_step_manual_input(self, user_input=None): + """Manual configuration.""" + if user_input: + self.onvif_config = user_input + return await self.async_step_auth() + + return self.async_show_form( + step_id="manual_input", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME): str, + vol.Required(CONF_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_PORT): int, + } + ), + ) + + async def async_step_auth(self, user_input=None): + """Username and Password configuration for ONVIF device.""" + if user_input: + self.onvif_config[CONF_USERNAME] = user_input[CONF_USERNAME] + self.onvif_config[CONF_PASSWORD] = user_input[CONF_PASSWORD] + return await self.async_step_profiles() + + return self.async_show_form( + step_id="auth", + data_schema=vol.Schema( + {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + ), + ) + + async def async_step_profiles(self, user_input=None): + """Fetch ONVIF device profiles.""" + errors = {} + + LOGGER.debug( + "Fetching profiles from ONVIF device %s", pformat(self.onvif_config) + ) + + device = get_device( + self.hass, + self.onvif_config[CONF_HOST], + self.onvif_config[CONF_PORT], + self.onvif_config[CONF_USERNAME], + self.onvif_config[CONF_PASSWORD], + ) + + await device.update_xaddrs() + + try: + # Get the MAC address to use as the unique ID for the config flow + if not self.device_id: + devicemgmt = device.create_devicemgmt_service() + network_interfaces = await devicemgmt.GetNetworkInterfaces() + for interface in network_interfaces: + if interface.Enabled: + self.device_id = interface.Info.HwAddress + + if self.device_id is None: + return self.async_abort(reason="no_mac") + + await self.async_set_unique_id(self.device_id, raise_on_progress=False) + self._abort_if_unique_id_configured( + updates={ + CONF_HOST: self.onvif_config[CONF_HOST], + CONF_PORT: self.onvif_config[CONF_PORT], + CONF_NAME: self.onvif_config[CONF_NAME], + } + ) + + if not self.onvif_config.get(CONF_PROFILE): + self.onvif_config[CONF_PROFILE] = [] + media_service = device.create_media_service() + profiles = await media_service.GetProfiles() + LOGGER.debug("Media Profiles %s", pformat(profiles)) + for key, profile in enumerate(profiles): + if profile.VideoEncoderConfiguration.Encoding != "H264": + continue + self.onvif_config[CONF_PROFILE].append(key) + + if not self.onvif_config[CONF_PROFILE]: + return self.async_abort(reason="no_h264") + + title = f"{self.onvif_config[CONF_NAME]} - {self.device_id}" + return self.async_create_entry(title=title, data=self.onvif_config) + + except exceptions.ONVIFError as err: + LOGGER.error( + "Couldn't setup ONVIF device '%s'. Error: %s", + self.onvif_config[CONF_NAME], + err, + ) + return self.async_abort(reason="onvif_error") + + except Fault: + errors["base"] = "connection_failed" + + return self.async_show_form(step_id="auth", errors=errors) + + async def async_step_import(self, user_input): + """Handle import.""" + self.onvif_config = user_input + return await self.async_step_profiles() + + +class OnvifOptionsFlowHandler(config_entries.OptionsFlow): + """Handle ONVIF options.""" + + def __init__(self, config_entry): + """Initialize ONVIF options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + + async def async_step_init(self, user_input=None): + """Manage the ONVIF options.""" + return await self.async_step_onvif_devices() + + async def async_step_onvif_devices(self, user_input=None): + """Manage the ONVIF devices options.""" + if user_input is not None: + self.options[CONF_EXTRA_ARGUMENTS] = user_input[CONF_EXTRA_ARGUMENTS] + self.options[CONF_RTSP_TRANSPORT] = user_input[CONF_RTSP_TRANSPORT] + return self.async_create_entry(title="", data=self.options) + + return self.async_show_form( + step_id="onvif_devices", + data_schema=vol.Schema( + { + vol.Optional( + CONF_EXTRA_ARGUMENTS, + default=self.config_entry.options.get( + CONF_EXTRA_ARGUMENTS, DEFAULT_ARGUMENTS + ), + ): str, + vol.Optional( + CONF_RTSP_TRANSPORT, + default=self.config_entry.options.get( + CONF_RTSP_TRANSPORT, RTSP_TRANS_PROTOCOLS[0] + ), + ): vol.In(RTSP_TRANS_PROTOCOLS), + } + ), + ) + + +def get_device(hass, host, port, username, password) -> ONVIFCamera: + """Get ONVIFCamera instance.""" + session = async_get_clientsession(hass) + transport = AsyncTransport(None, session=session) + device = ONVIFCamera( + host, + port, + username, + password, + f"{os.path.dirname(onvif.__file__)}/wsdl/", + transport=transport, + ) + + return device diff --git a/homeassistant/components/onvif/const.py b/homeassistant/components/onvif/const.py new file mode 100644 index 00000000000..c2eb2604a26 --- /dev/null +++ b/homeassistant/components/onvif/const.py @@ -0,0 +1,47 @@ +"""Constants for the onvif component.""" +import logging + +LOGGER = logging.getLogger(__package__) + +DOMAIN = "onvif" +ONVIF_DATA = "onvif" +ENTITIES = "entities" + +DEFAULT_NAME = "ONVIF Camera" +DEFAULT_PORT = 5000 +DEFAULT_USERNAME = "admin" +DEFAULT_PASSWORD = "888888" +DEFAULT_ARGUMENTS = "-pred 1" +DEFAULT_PROFILE = 0 + +CONF_DEVICE_ID = "deviceid" +CONF_PROFILE = "profile" +CONF_RTSP_TRANSPORT = "rtsp_transport" + +RTSP_TRANS_PROTOCOLS = ["tcp", "udp", "udp_multicast", "http"] + +ATTR_PAN = "pan" +ATTR_TILT = "tilt" +ATTR_ZOOM = "zoom" +ATTR_DISTANCE = "distance" +ATTR_SPEED = "speed" +ATTR_MOVE_MODE = "move_mode" +ATTR_CONTINUOUS_DURATION = "continuous_duration" +ATTR_PRESET = "preset" + +DIR_UP = "UP" +DIR_DOWN = "DOWN" +DIR_LEFT = "LEFT" +DIR_RIGHT = "RIGHT" +ZOOM_OUT = "ZOOM_OUT" +ZOOM_IN = "ZOOM_IN" +PAN_FACTOR = {DIR_RIGHT: 1, DIR_LEFT: -1} +TILT_FACTOR = {DIR_UP: 1, DIR_DOWN: -1} +ZOOM_FACTOR = {ZOOM_IN: 1, ZOOM_OUT: -1} +CONTINUOUS_MOVE = "ContinuousMove" +RELATIVE_MOVE = "RelativeMove" +ABSOLUTE_MOVE = "AbsoluteMove" +GOTOPRESET_MOVE = "GotoPreset" + +SERVICE_PTZ = "ptz" +ENTITIES = "entities" diff --git a/homeassistant/components/onvif/manifest.json b/homeassistant/components/onvif/manifest.json index a927fd9072b..5931f3c71c3 100644 --- a/homeassistant/components/onvif/manifest.json +++ b/homeassistant/components/onvif/manifest.json @@ -2,7 +2,8 @@ "domain": "onvif", "name": "ONVIF", "documentation": "https://www.home-assistant.io/integrations/onvif", - "requirements": ["onvif-zeep-async==0.2.0"], + "requirements": ["onvif-zeep-async==0.2.0", "WSDiscovery==2.0.0"], "dependencies": ["ffmpeg"], - "codeowners": [] + "codeowners": ["@hunterjm"], + "config_flow": true } diff --git a/homeassistant/components/onvif/strings.json b/homeassistant/components/onvif/strings.json new file mode 100644 index 00000000000..e0de8857edf --- /dev/null +++ b/homeassistant/components/onvif/strings.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "ONVIF device is already configured.", + "already_in_progress": "Config flow for ONVIF device is already in progress.", + "onvif_error": "Error setting up ONVIF device. Check logs for more information.", + "no_h264": "There were no H264 streams available. Check the profile configuration on your device.", + "no_mac": "Could not configure unique ID for ONVIF device." + }, + "error": { + "connection_failed": "Could not connect to ONVIF service with provided credentials." + }, + "step": { + "user": { + "title": "ONVIF device setup", + "description": "By clicking submit, we will search your network for ONVIF devices that support Profile S.\n\nSome manufacturers have started to disable ONVIF by default. Please ensure ONVIF is enabled in your camera's configuration." + }, + "device": { + "data": { + "host": "Select discovered ONVIF device" + }, + "title": "Select ONVIF device" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Configure ONVIF device" + }, + "auth": { + "title": "Configure authentication", + "data": { + "username": "Username", + "password": "Password" + } + }, + "configure_profile": { + "description": "Create camera entity for {profile} at {resolution} resolution?", + "title": "Configure Profiles", + "data": { + "include": "Create camera entity" + } + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Extra FFMPEG arguments", + "rtsp_transport": "RTSP transport mechanism" + }, + "title": "ONVIF Device Options" + } + } + } +} diff --git a/homeassistant/components/onvif/translations/en.json b/homeassistant/components/onvif/translations/en.json new file mode 100644 index 00000000000..066efed60f2 --- /dev/null +++ b/homeassistant/components/onvif/translations/en.json @@ -0,0 +1,59 @@ +{ + "config": { + "abort": { + "already_configured": "ONVIF device is already configured.", + "already_in_progress": "Config flow for ONVIF device is already in progress.", + "no_h264": "There were no H264 streams available. Check the profile configuration on your device.", + "no_mac": "Could not configure unique ID for ONVIF device.", + "onvif_error": "Error setting up ONVIF device. Check logs for more information." + }, + "error": { + "connection_failed": "Could not connect to ONVIF service with provided credentials." + }, + "step": { + "auth": { + "data": { + "password": "Password", + "username": "Username" + }, + "title": "Configure authentication" + }, + "configure_profile": { + "data": { + "include": "Create camera entity" + }, + "description": "Create camera entity for {profile} at {resolution} resolution?", + "title": "Configure Profiles" + }, + "device": { + "data": { + "host": "Select discovered ONVIF device" + }, + "title": "Select ONVIF device" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Configure ONVIF device" + }, + "user": { + "description": "By clicking submit, we will search your network for ONVIF devices that support Profile S.\n\nSome manufacturers have started to disable ONVIF by default. Please ensure ONVIF is enabled in your camera's configuration.", + "title": "ONVIF device setup" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Extra FFMPEG arguments", + "rtsp_transport": "RTSP transport mechanism" + }, + "title": "ONVIF Device Options" + } + } + }, + "title": "ONVIF" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index a65d1e2b52a..1f08da03648 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -91,6 +91,7 @@ FLOWS = [ "nuheat", "nut", "nws", + "onvif", "opentherm_gw", "openuv", "owntracks", diff --git a/requirements_all.txt b/requirements_all.txt index d166d68ae7f..e35d2f980ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -101,6 +101,9 @@ TwitterAPI==2.5.11 # homeassistant.components.tof # VL53L1X2==0.1.5 +# homeassistant.components.onvif +WSDiscovery==2.0.0 + # homeassistant.components.waze_travel_time WazeRouteCalculator==0.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 607acf46478..130e6568fff 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -23,6 +23,9 @@ PyTransportNSW==0.1.1 # homeassistant.components.remember_the_milk RtmAPI==0.7.2 +# homeassistant.components.onvif +WSDiscovery==2.0.0 + # homeassistant.components.yessssms YesssSMS==0.4.1 @@ -389,6 +392,9 @@ numpy==1.18.2 # homeassistant.components.google oauth2client==4.0.0 +# homeassistant.components.onvif +onvif-zeep-async==0.2.0 + # homeassistant.components.openerz openerz-api==0.1.0 diff --git a/tests/components/onvif/__init__.py b/tests/components/onvif/__init__.py new file mode 100644 index 00000000000..433a6392f12 --- /dev/null +++ b/tests/components/onvif/__init__.py @@ -0,0 +1 @@ +"""Tests for the ONVIF integration.""" diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py new file mode 100644 index 00000000000..685e1e3fc4d --- /dev/null +++ b/tests/components/onvif/test_config_flow.py @@ -0,0 +1,508 @@ +"""Test ONVIF config flow.""" +from asyncio import Future + +from asynctest import MagicMock, patch +from onvif.exceptions import ONVIFError +from zeep.exceptions import Fault + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components.onvif import config_flow + +from tests.common import MockConfigEntry + +URN = "urn:uuid:123456789" +NAME = "TestCamera" +HOST = "1.2.3.4" +PORT = 80 +USERNAME = "admin" +PASSWORD = "12345" +MAC = "aa:bb:cc:dd:ee" + +DISCOVERY = [ + { + "EPR": URN, + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + "MAC": MAC, + }, + { + "EPR": "urn:uuid:987654321", + config_flow.CONF_NAME: "TestCamera2", + config_flow.CONF_HOST: "5.6.7.8", + config_flow.CONF_PORT: PORT, + "MAC": "ee:dd:cc:bb:aa", + }, +] + + +def setup_mock_onvif_device( + mock_device, with_h264=True, two_profiles=False, with_interfaces=True +): + """Prepare mock ONVIF device.""" + devicemgmt = MagicMock() + + interface = MagicMock() + interface.Enabled = True + interface.Info.HwAddress = MAC + + devicemgmt.GetNetworkInterfaces.return_value = Future() + devicemgmt.GetNetworkInterfaces.return_value.set_result( + [interface] if with_interfaces else [] + ) + + media_service = MagicMock() + + profile1 = MagicMock() + profile1.VideoEncoderConfiguration.Encoding = "H264" if with_h264 else "MJPEG" + profile2 = MagicMock() + profile2.VideoEncoderConfiguration.Encoding = "H264" if two_profiles else "MJPEG" + + media_service.GetProfiles.return_value = Future() + media_service.GetProfiles.return_value.set_result([profile1, profile2]) + + mock_device.update_xaddrs.return_value = Future() + mock_device.update_xaddrs.return_value.set_result(True) + mock_device.create_devicemgmt_service = MagicMock(return_value=devicemgmt) + mock_device.create_media_service = MagicMock(return_value=media_service) + + def mock_constructor( + host, + port, + user, + passwd, + wsdl_dir, + encrypt=True, + no_cache=False, + adjust_time=False, + transport=None, + ): + """Fake the controller constructor.""" + return mock_device + + mock_device.side_effect = mock_constructor + + +def setup_mock_discovery( + mock_discovery, with_name=False, with_mac=False, two_devices=False +): + """Prepare mock discovery result.""" + services = [] + for item in DISCOVERY: + service = MagicMock() + service.getXAddrs = MagicMock( + return_value=[ + f"http://{item[config_flow.CONF_HOST]}:{item[config_flow.CONF_PORT]}/onvif/device_service" + ] + ) + service.getEPR = MagicMock(return_value=item["EPR"]) + scopes = [] + if with_name: + scope = MagicMock() + scope.getValue = MagicMock( + return_value=f"onvif://www.onvif.org/name/{item[config_flow.CONF_NAME]}" + ) + scopes.append(scope) + if with_mac: + scope = MagicMock() + scope.getValue = MagicMock( + return_value=f"onvif://www.onvif.org/mac/{item['MAC']}" + ) + scopes.append(scope) + service.getScopes = MagicMock(return_value=scopes) + services.append(service) + mock_discovery.return_value = services + + +def setup_mock_camera(mock_camera): + """Prepare mock HASS camera.""" + mock_camera.async_initialize.return_value = Future() + mock_camera.async_initialize.return_value.set_result(True) + + def mock_constructor(hass, config): + """Fake the controller constructor.""" + return mock_camera + + mock_camera.side_effect = mock_constructor + + +async def setup_onvif_integration( + hass, config=None, options=None, unique_id=MAC, entry_id="1", source="user", +): + """Create an ONVIF config entry.""" + if not config: + config = { + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + config_flow.CONF_PROFILE: [0], + } + + config_entry = MockConfigEntry( + domain=config_flow.DOMAIN, + source=source, + data={**config}, + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + options=options or {}, + entry_id=entry_id, + unique_id=unique_id, + ) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_device, patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, patch( + "homeassistant.components.onvif.camera.ONVIFHassCamera" + ) as mock_camera: + setup_mock_onvif_device(mock_device, two_profiles=True) + # no discovery + mock_discovery.return_value = [] + setup_mock_camera(mock_camera) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + return config_entry + + +async def test_flow_discovered_devices(hass): + """Test that config flow works for discovered devices.""" + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_device, patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, patch( + "homeassistant.components.onvif.camera.ONVIFHassCamera" + ) as mock_camera: + setup_mock_onvif_device(mock_device) + setup_mock_discovery(mock_discovery) + setup_mock_camera(mock_camera) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "device" + assert len(result["data_schema"].schema[config_flow.CONF_HOST].container) == 3 + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={config_flow.CONF_HOST: f"{URN} ({HOST})"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "auth" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == f"{URN} - {MAC}" + assert result["data"] == { + config_flow.CONF_NAME: URN, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + config_flow.CONF_PROFILE: [0], + } + + +async def test_flow_discovered_devices_ignore_configured_manual_input(hass): + """Test that config flow discovery ignores configured devices.""" + await setup_onvif_integration(hass) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_device, patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, patch( + "homeassistant.components.onvif.camera.ONVIFHassCamera" + ) as mock_camera: + setup_mock_onvif_device(mock_device) + setup_mock_discovery(mock_discovery, with_mac=True) + setup_mock_camera(mock_camera) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "device" + assert len(result["data_schema"].schema[config_flow.CONF_HOST].container) == 2 + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={config_flow.CONF_HOST: config_flow.CONF_MANUAL_INPUT}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "manual_input" + + +async def test_flow_discovery_ignore_existing_and_abort(hass): + """Test that config flow discovery ignores setup devices.""" + await setup_onvif_integration(hass) + await setup_onvif_integration( + hass, + config={ + config_flow.CONF_NAME: DISCOVERY[1]["EPR"], + config_flow.CONF_HOST: DISCOVERY[1][config_flow.CONF_HOST], + config_flow.CONF_PORT: DISCOVERY[1][config_flow.CONF_PORT], + config_flow.CONF_USERNAME: "", + config_flow.CONF_PASSWORD: "", + }, + unique_id=DISCOVERY[1]["MAC"], + entry_id="2", + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_device, patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, patch( + "homeassistant.components.onvif.camera.ONVIFHassCamera" + ) as mock_camera: + setup_mock_onvif_device(mock_device) + setup_mock_discovery(mock_discovery, with_name=True, with_mac=True) + setup_mock_camera(mock_camera) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} + ) + + # It should skip to manual entry if the only devices are already configured + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "manual_input" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "auth" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + # It should abort if already configured and entered manually + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + + +async def test_flow_manual_entry(hass): + """Test that config flow works for discovered devices.""" + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, context={"source": "user"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_device, patch( + "homeassistant.components.onvif.config_flow.wsdiscovery" + ) as mock_discovery, patch( + "homeassistant.components.onvif.camera.ONVIFHassCamera" + ) as mock_camera: + setup_mock_onvif_device(mock_device, two_profiles=True) + # no discovery + mock_discovery.return_value = [] + setup_mock_camera(mock_camera) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "manual_input" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "auth" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == f"{NAME} - {MAC}" + assert result["data"] == { + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + config_flow.CONF_PROFILE: [0, 1], + } + + +async def test_flow_import_no_mac(hass): + """Test that config flow fails when no MAC available.""" + with patch("homeassistant.components.onvif.config_flow.get_device") as mock_device: + setup_mock_onvif_device(mock_device, with_interfaces=False) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + config_flow.CONF_PROFILE: [0], + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_mac" + + +async def test_flow_import_no_h264(hass): + """Test that config flow fails when no MAC available.""" + with patch("homeassistant.components.onvif.config_flow.get_device") as mock_device: + setup_mock_onvif_device(mock_device, with_h264=False) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_h264" + + +async def test_flow_import_onvif_api_error(hass): + """Test that config flow fails when ONVIF API fails.""" + with patch("homeassistant.components.onvif.config_flow.get_device") as mock_device: + setup_mock_onvif_device(mock_device) + mock_device.create_devicemgmt_service = MagicMock( + side_effect=ONVIFError("Could not get device mgmt service") + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "onvif_error" + + +async def test_flow_import_onvif_auth_error(hass): + """Test that config flow fails when ONVIF API fails.""" + with patch("homeassistant.components.onvif.config_flow.get_device") as mock_device: + setup_mock_onvif_device(mock_device) + mock_device.create_devicemgmt_service = MagicMock( + side_effect=Fault("Auth Error") + ) + + result = await hass.config_entries.flow.async_init( + config_flow.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={ + config_flow.CONF_NAME: NAME, + config_flow.CONF_HOST: HOST, + config_flow.CONF_PORT: PORT, + config_flow.CONF_USERNAME: USERNAME, + config_flow.CONF_PASSWORD: PASSWORD, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "auth" + assert result["errors"]["base"] == "connection_failed" + + +async def test_option_flow(hass): + """Test config flow options.""" + entry = await setup_onvif_integration(hass) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "onvif_devices" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + config_flow.CONF_EXTRA_ARGUMENTS: "", + config_flow.CONF_RTSP_TRANSPORT: config_flow.RTSP_TRANS_PROTOCOLS[1], + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == { + config_flow.CONF_EXTRA_ARGUMENTS: "", + config_flow.CONF_RTSP_TRANSPORT: config_flow.RTSP_TRANS_PROTOCOLS[1], + } From a2896b4764330860421acfccf317d922bbee60ed Mon Sep 17 00:00:00 2001 From: Vilppu Vuorinen Date: Fri, 1 May 2020 09:56:03 +0300 Subject: [PATCH 187/511] Add flow and return sensors for MELCloud ATW device (#34693) --- .../components/melcloud/manifest.json | 2 +- homeassistant/components/melcloud/sensor.py | 18 +++++++++++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/melcloud/manifest.json b/homeassistant/components/melcloud/manifest.json index 4747059345f..aac8db678f9 100644 --- a/homeassistant/components/melcloud/manifest.json +++ b/homeassistant/components/melcloud/manifest.json @@ -3,6 +3,6 @@ "name": "MELCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/melcloud", - "requirements": ["pymelcloud==2.4.1"], + "requirements": ["pymelcloud==2.5.2"], "codeowners": ["@vilppuvuorinen"] } diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index 9dee01c2fba..6ca69aefe2a 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -65,7 +65,23 @@ ATW_ZONE_SENSORS = { ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda zone: zone.room_temperature, ATTR_ENABLED_FN: lambda x: True, - } + }, + "flow_temperature": { + ATTR_MEASUREMENT_NAME: "Flow Temperature", + ATTR_ICON: "mdi:thermometer", + ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + ATTR_VALUE_FN: lambda zone: zone.flow_temperature, + ATTR_ENABLED_FN: lambda x: True, + }, + "return_temperature": { + ATTR_MEASUREMENT_NAME: "Flow Return Temperature", + ATTR_ICON: "mdi:thermometer", + ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, + ATTR_VALUE_FN: lambda zone: zone.return_temperature, + ATTR_ENABLED_FN: lambda x: True, + }, } _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index e35d2f980ec..62a27f90c38 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1411,7 +1411,7 @@ pymailgunner==1.4 pymediaroom==0.6.4 # homeassistant.components.melcloud -pymelcloud==2.4.1 +pymelcloud==2.5.2 # homeassistant.components.somfy pymfy==0.7.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 130e6568fff..33bd8c3f9d0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -576,7 +576,7 @@ pylitejet==0.1 pymailgunner==1.4 # homeassistant.components.melcloud -pymelcloud==2.4.1 +pymelcloud==2.5.2 # homeassistant.components.somfy pymfy==0.7.1 From 8ec5e53cf43a2c6072706d3c6a594c1a3c8590d2 Mon Sep 17 00:00:00 2001 From: Guillaume DELVIT Date: Fri, 1 May 2020 10:29:22 +0200 Subject: [PATCH 188/511] Add full options to serial sensor platform (#34962) Improve Serial sensor platform with full options for configuring the serial port. bytesize, parity, stopbits, xonxoff, rtscts, dsrdtr. The default values are unchanged so it is 100% compatible with previous config. --- homeassistant/components/serial/sensor.py | 113 +++++++++++++++++++++- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/serial/sensor.py b/homeassistant/components/serial/sensor.py index 905167ea3ff..e0bf23a2514 100644 --- a/homeassistant/components/serial/sensor.py +++ b/homeassistant/components/serial/sensor.py @@ -17,9 +17,21 @@ _LOGGER = logging.getLogger(__name__) CONF_SERIAL_PORT = "serial_port" CONF_BAUDRATE = "baudrate" +CONF_BYTESIZE = "bytesize" +CONF_PARITY = "parity" +CONF_STOPBITS = "stopbits" +CONF_XONXOFF = "xonxoff" +CONF_RTSCTS = "rtscts" +CONF_DSRDTR = "dsrdtr" DEFAULT_NAME = "Serial Sensor" DEFAULT_BAUDRATE = 9600 +DEFAULT_BYTESIZE = serial_asyncio.serial.EIGHTBITS +DEFAULT_PARITY = serial_asyncio.serial.PARITY_NONE +DEFAULT_STOPBITS = serial_asyncio.serial.STOPBITS_ONE +DEFAULT_XONXOFF = False +DEFAULT_RTSCTS = False +DEFAULT_DSRDTR = False PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { @@ -27,6 +39,33 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_BAUDRATE, default=DEFAULT_BAUDRATE): cv.positive_int, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_BYTESIZE, default=DEFAULT_BYTESIZE): vol.In( + [ + serial_asyncio.serial.FIVEBITS, + serial_asyncio.serial.SIXBITS, + serial_asyncio.serial.SEVENBITS, + serial_asyncio.serial.EIGHTBITS, + ] + ), + vol.Optional(CONF_PARITY, default=DEFAULT_PARITY): vol.In( + [ + serial_asyncio.serial.PARITY_NONE, + serial_asyncio.serial.PARITY_EVEN, + serial_asyncio.serial.PARITY_ODD, + serial_asyncio.serial.PARITY_MARK, + serial_asyncio.serial.PARITY_SPACE, + ] + ), + vol.Optional(CONF_STOPBITS, default=DEFAULT_STOPBITS): vol.In( + [ + serial_asyncio.serial.STOPBITS_ONE, + serial_asyncio.serial.STOPBITS_ONE_POINT_FIVE, + serial_asyncio.serial.STOPBITS_TWO, + ] + ), + vol.Optional(CONF_XONXOFF, default=DEFAULT_XONXOFF): cv.boolean, + vol.Optional(CONF_RTSCTS, default=DEFAULT_RTSCTS): cv.boolean, + vol.Optional(CONF_DSRDTR, default=DEFAULT_DSRDTR): cv.boolean, } ) @@ -36,12 +75,29 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= name = config.get(CONF_NAME) port = config.get(CONF_SERIAL_PORT) baudrate = config.get(CONF_BAUDRATE) + bytesize = config.get(CONF_BYTESIZE) + parity = config.get(CONF_PARITY) + stopbits = config.get(CONF_STOPBITS) + xonxoff = config.get(CONF_XONXOFF) + rtscts = config.get(CONF_RTSCTS) + dsrdtr = config.get(CONF_DSRDTR) value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: value_template.hass = hass - sensor = SerialSensor(name, port, baudrate, value_template) + sensor = SerialSensor( + name, + port, + baudrate, + bytesize, + parity, + stopbits, + xonxoff, + rtscts, + dsrdtr, + value_template, + ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, sensor.stop_serial_read) async_add_entities([sensor], True) @@ -50,12 +106,30 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class SerialSensor(Entity): """Representation of a Serial sensor.""" - def __init__(self, name, port, baudrate, value_template): + def __init__( + self, + name, + port, + baudrate, + bytesize, + parity, + stopbits, + xonxoff, + rtscts, + dsrdtr, + value_template, + ): """Initialize the Serial sensor.""" self._name = name self._state = None self._port = port self._baudrate = baudrate + self._bytesize = bytesize + self._parity = parity + self._stopbits = stopbits + self._xonxoff = xonxoff + self._rtscts = rtscts + self._dsrdtr = dsrdtr self._serial_loop_task = None self._template = value_template self._attributes = None @@ -63,17 +137,46 @@ class SerialSensor(Entity): async def async_added_to_hass(self): """Handle when an entity is about to be added to Home Assistant.""" self._serial_loop_task = self.hass.loop.create_task( - self.serial_read(self._port, self._baudrate) + self.serial_read( + self._port, + self._baudrate, + self._bytesize, + self._parity, + self._stopbits, + self._xonxoff, + self._rtscts, + self._dsrdtr, + ) ) - async def serial_read(self, device, rate, **kwargs): + async def serial_read( + self, + device, + baudrate, + bytesize, + parity, + stopbits, + xonxoff, + rtscts, + dsrdtr, + **kwargs, + ): """Read the data from the port.""" logged_error = False while True: try: reader, _ = await serial_asyncio.open_serial_connection( - url=device, baudrate=rate, **kwargs + url=device, + baudrate=baudrate, + bytesize=bytesize, + parity=parity, + stopbits=stopbits, + xonxoff=xonxoff, + rtscts=rtscts, + dsrdtr=dsrdtr, + **kwargs, ) + except SerialException as exc: if not logged_error: _LOGGER.exception( From 66d3832be904ae6207f242a9a022a132d9c1f4dd Mon Sep 17 00:00:00 2001 From: Vilppu Vuorinen Date: Fri, 1 May 2020 14:33:46 +0300 Subject: [PATCH 189/511] Fix MELCloud temperature unit (#35003) The MELCLoud API produces and consumes only Celsius. --- homeassistant/components/melcloud/climate.py | 18 ++++++------------ homeassistant/components/melcloud/const.py | 9 --------- homeassistant/components/melcloud/sensor.py | 16 ++++++++-------- .../components/melcloud/water_heater.py | 4 ++-- 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/melcloud/climate.py b/homeassistant/components/melcloud/climate.py index bc2ac7f1026..ed2fefc823d 100644 --- a/homeassistant/components/melcloud/climate.py +++ b/homeassistant/components/melcloud/climate.py @@ -31,7 +31,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.typing import HomeAssistantType -from homeassistant.util.temperature import convert as convert_temperature from . import MelCloudDevice from .const import ( @@ -44,7 +43,6 @@ from .const import ( DOMAIN, SERVICE_SET_VANE_HORIZONTAL, SERVICE_SET_VANE_VERTICAL, - TEMP_UNIT_LOOKUP, ) SCAN_INTERVAL = timedelta(seconds=60) @@ -169,7 +167,7 @@ class AtaDeviceClimate(MelCloudClimate): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def hvac_mode(self) -> str: @@ -281,9 +279,7 @@ class AtaDeviceClimate(MelCloudClimate): if min_value is not None: return min_value - return convert_temperature( - DEFAULT_MIN_TEMP, TEMP_CELSIUS, self.temperature_unit - ) + return DEFAULT_MIN_TEMP @property def max_temp(self) -> float: @@ -292,9 +288,7 @@ class AtaDeviceClimate(MelCloudClimate): if max_value is not None: return max_value - return convert_temperature( - DEFAULT_MAX_TEMP, TEMP_CELSIUS, self.temperature_unit - ) + return DEFAULT_MAX_TEMP class AtwDeviceZoneClimate(MelCloudClimate): @@ -331,7 +325,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def hvac_mode(self) -> str: @@ -391,7 +385,7 @@ class AtwDeviceZoneClimate(MelCloudClimate): MELCloud API does not expose radiator zone temperature limits. """ - return convert_temperature(10, TEMP_CELSIUS, self.temperature_unit) + return 10 @property def max_temp(self) -> float: @@ -399,4 +393,4 @@ class AtwDeviceZoneClimate(MelCloudClimate): MELCloud API does not expose radiator zone temperature limits. """ - return convert_temperature(30, TEMP_CELSIUS, self.temperature_unit) + return 30 diff --git a/homeassistant/components/melcloud/const.py b/homeassistant/components/melcloud/const.py index d58f483d441..27cffb75223 100644 --- a/homeassistant/components/melcloud/const.py +++ b/homeassistant/components/melcloud/const.py @@ -1,7 +1,4 @@ """Constants for the MELCloud Climate integration.""" -from pymelcloud.const import UNIT_TEMP_CELSIUS, UNIT_TEMP_FAHRENHEIT - -from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT DOMAIN = "melcloud" @@ -15,9 +12,3 @@ ATTR_VANE_VERTICAL_POSITIONS = "vane_vertical_positions" SERVICE_SET_VANE_HORIZONTAL = "set_vane_horizontal" SERVICE_SET_VANE_VERTICAL = "set_vane_vertical" - -TEMP_UNIT_LOOKUP = { - UNIT_TEMP_CELSIUS: TEMP_CELSIUS, - UNIT_TEMP_FAHRENHEIT: TEMP_FAHRENHEIT, -} -TEMP_UNIT_REVERSE_LOOKUP = {v: k for k, v in TEMP_UNIT_LOOKUP.items()} diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index 6ca69aefe2a..ed64647071b 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -12,11 +12,11 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity from . import MelCloudDevice -from .const import DOMAIN, TEMP_UNIT_LOOKUP +from .const import DOMAIN ATTR_MEASUREMENT_NAME = "measurement_name" ATTR_ICON = "icon" -ATTR_UNIT_FN = "unit_fn" +ATTR_UNIT = "unit" ATTR_DEVICE_CLASS = "device_class" ATTR_VALUE_FN = "value_fn" ATTR_ENABLED_FN = "enabled" @@ -25,7 +25,7 @@ ATA_SENSORS = { "room_temperature": { ATTR_MEASUREMENT_NAME: "Room Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.room_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -33,7 +33,7 @@ ATA_SENSORS = { "energy": { ATTR_MEASUREMENT_NAME: "Energy", ATTR_ICON: "mdi:factory", - ATTR_UNIT_FN: lambda x: ENERGY_KILO_WATT_HOUR, + ATTR_UNIT: ENERGY_KILO_WATT_HOUR, ATTR_DEVICE_CLASS: None, ATTR_VALUE_FN: lambda x: x.device.total_energy_consumed, ATTR_ENABLED_FN: lambda x: x.device.has_energy_consumed_meter, @@ -43,7 +43,7 @@ ATW_SENSORS = { "outside_temperature": { ATTR_MEASUREMENT_NAME: "Outside Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.outside_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -51,7 +51,7 @@ ATW_SENSORS = { "tank_temperature": { ATTR_MEASUREMENT_NAME: "Tank Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda x: x.device.tank_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -61,7 +61,7 @@ ATW_ZONE_SENSORS = { "room_temperature": { ATTR_MEASUREMENT_NAME: "Room Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda zone: zone.room_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -147,7 +147,7 @@ class MelDeviceSensor(Entity): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return self._def[ATTR_UNIT_FN](self._api) + return self._def[ATTR_UNIT] @property def device_class(self): diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index fa7aff2b640..ce1b1ae15cc 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -18,7 +18,7 @@ from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.typing import HomeAssistantType from . import DOMAIN, MelCloudDevice -from .const import ATTR_STATUS, TEMP_UNIT_LOOKUP +from .const import ATTR_STATUS async def async_setup_entry( @@ -80,7 +80,7 @@ class AtwWaterHeater(WaterHeaterDevice): @property def temperature_unit(self) -> str: """Return the unit of measurement used by the platform.""" - return TEMP_UNIT_LOOKUP.get(self._device.temp_unit, TEMP_CELSIUS) + return TEMP_CELSIUS @property def current_operation(self) -> Optional[str]: From be58c4f71a6bac4d56b431647183748f3c4e1d05 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Fri, 1 May 2020 04:34:29 -0700 Subject: [PATCH 190/511] Fix unknown exception being caught (#35005) --- homeassistant/components/roomba/config_flow.py | 2 -- homeassistant/components/roomba/strings.json | 1 - 2 files changed, 3 deletions(-) diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 3668984a41f..e323150fba3 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -86,8 +86,6 @@ class RoombaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): info = await validate_input(self.hass, user_input) except CannotConnect: errors = {"base": "cannot_connect"} - except Exception: # pylint: disable=broad-except - errors = {"base": "unknown"} if "base" not in errors: await async_disconnect_or_timeout(self.hass, info[ROOMBA_SESSION]) diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index a679b2fdbb5..c15c5f5893a 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -15,7 +15,6 @@ } }, "error": { - "unknown": "Unexpected error", "cannot_connect": "Failed to connect, please try again" } }, From 1cfa46d80b778bdc3198dd5a74d8a0b30ebc1817 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 1 May 2020 15:56:38 +0200 Subject: [PATCH 191/511] Fix CI, incomplete change in melcloud (#35016) --- homeassistant/components/melcloud/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/melcloud/sensor.py b/homeassistant/components/melcloud/sensor.py index ed64647071b..774b66ab67e 100644 --- a/homeassistant/components/melcloud/sensor.py +++ b/homeassistant/components/melcloud/sensor.py @@ -69,7 +69,7 @@ ATW_ZONE_SENSORS = { "flow_temperature": { ATTR_MEASUREMENT_NAME: "Flow Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda zone: zone.flow_temperature, ATTR_ENABLED_FN: lambda x: True, @@ -77,7 +77,7 @@ ATW_ZONE_SENSORS = { "return_temperature": { ATTR_MEASUREMENT_NAME: "Flow Return Temperature", ATTR_ICON: "mdi:thermometer", - ATTR_UNIT_FN: lambda x: TEMP_UNIT_LOOKUP.get(x.device.temp_unit, TEMP_CELSIUS), + ATTR_UNIT: TEMP_CELSIUS, ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, ATTR_VALUE_FN: lambda zone: zone.return_temperature, ATTR_ENABLED_FN: lambda x: True, From f3d79104a7503a2d7582a1b68e1b58b6cc1da467 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 1 May 2020 16:29:14 +0200 Subject: [PATCH 192/511] Rename WaterHeaterDevice to WaterHeaterEntity (#34675) * Rename WaterHeaterDevice to WaterHeaterEntity * Fix stale name Co-authored-by: Martin Hjelmare --- homeassistant/components/demo/water_heater.py | 4 ++-- homeassistant/components/econet/water_heater.py | 4 ++-- homeassistant/components/evohome/water_heater.py | 4 ++-- homeassistant/components/geniushub/water_heater.py | 4 ++-- homeassistant/components/hive/water_heater.py | 4 ++-- homeassistant/components/incomfort/water_heater.py | 4 ++-- homeassistant/components/melcloud/water_heater.py | 4 ++-- homeassistant/components/tado/water_heater.py | 4 ++-- homeassistant/components/vicare/water_heater.py | 4 ++-- homeassistant/components/water_heater/__init__.py | 14 +++++++++++++- homeassistant/components/wink/water_heater.py | 4 ++-- tests/components/water_heater/test_init.py | 12 ++++++++++++ 12 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 tests/components/water_heater/test_init.py diff --git a/homeassistant/components/demo/water_heater.py b/homeassistant/components/demo/water_heater.py index f9aca141245..0b96bbf75f8 100644 --- a/homeassistant/components/demo/water_heater.py +++ b/homeassistant/components/demo/water_heater.py @@ -3,7 +3,7 @@ from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): await async_setup_platform(hass, {}, async_add_entities) -class DemoWaterHeater(WaterHeaterDevice): +class DemoWaterHeater(WaterHeaterEntity): """Representation of a demo water_heater device.""" def __init__( diff --git a/homeassistant/components/econet/water_heater.py b/homeassistant/components/econet/water_heater.py index 59afe1351f5..0c31e3e50e0 100644 --- a/homeassistant/components/econet/water_heater.py +++ b/homeassistant/components/econet/water_heater.py @@ -16,7 +16,7 @@ from homeassistant.components.water_heater import ( STATE_PERFORMANCE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import ( ATTR_ENTITY_ID, @@ -120,7 +120,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class EcoNetWaterHeater(WaterHeaterDevice): +class EcoNetWaterHeater(WaterHeaterEntity): """Representation of an EcoNet water heater.""" def __init__(self, water_heater): diff --git a/homeassistant/components/evohome/water_heater.py b/homeassistant/components/evohome/water_heater.py index 20aa0710d0d..846c8c09155 100644 --- a/homeassistant/components/evohome/water_heater.py +++ b/homeassistant/components/evohome/water_heater.py @@ -5,7 +5,7 @@ from typing import List from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import PRECISION_TENTHS, PRECISION_WHOLE, STATE_OFF, STATE_ON from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -43,7 +43,7 @@ async def async_setup_platform( async_add_entities([new_entity], update_before_add=True) -class EvoDHW(EvoChild, WaterHeaterDevice): +class EvoDHW(EvoChild, WaterHeaterEntity): """Base for a Honeywell TCC DHW controller (aka boiler).""" def __init__(self, evo_broker, evo_device) -> None: diff --git a/homeassistant/components/geniushub/water_heater.py b/homeassistant/components/geniushub/water_heater.py index e7e3278eaf6..51fdce4a6d7 100644 --- a/homeassistant/components/geniushub/water_heater.py +++ b/homeassistant/components/geniushub/water_heater.py @@ -4,7 +4,7 @@ from typing import List from homeassistant.components.water_heater import ( SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import STATE_OFF from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -49,7 +49,7 @@ async def async_setup_platform( ) -class GeniusWaterHeater(GeniusHeatingZone, WaterHeaterDevice): +class GeniusWaterHeater(GeniusHeatingZone, WaterHeaterEntity): """Representation of a Genius Hub water_heater device.""" def __init__(self, broker, zone) -> None: diff --git a/homeassistant/components/hive/water_heater.py b/homeassistant/components/hive/water_heater.py index d7d98426df5..693fd6f322b 100644 --- a/homeassistant/components/hive/water_heater.py +++ b/homeassistant/components/hive/water_heater.py @@ -4,7 +4,7 @@ from homeassistant.components.water_heater import ( STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import TEMP_CELSIUS @@ -29,7 +29,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(devs) -class HiveWaterHeater(HiveEntity, WaterHeaterDevice): +class HiveWaterHeater(HiveEntity, WaterHeaterEntity): """Hive Water Heater Device.""" @property diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index 88370acf166..da6e6d89315 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -7,7 +7,7 @@ from aiohttp import ClientResponseError from homeassistant.components.water_heater import ( DOMAIN as WATER_HEATER_DOMAIN, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -30,7 +30,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([IncomfortWaterHeater(client, h) for h in heaters]) -class IncomfortWaterHeater(IncomfortEntity, WaterHeaterDevice): +class IncomfortWaterHeater(IncomfortEntity, WaterHeaterEntity): """Representation of an InComfort/Intouch water_heater device.""" def __init__(self, client, heater) -> None: diff --git a/homeassistant/components/melcloud/water_heater.py b/homeassistant/components/melcloud/water_heater.py index ce1b1ae15cc..ae10b5140f7 100644 --- a/homeassistant/components/melcloud/water_heater.py +++ b/homeassistant/components/melcloud/water_heater.py @@ -11,7 +11,7 @@ from pymelcloud.device import PROPERTY_POWER from homeassistant.components.water_heater import ( SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import TEMP_CELSIUS @@ -35,7 +35,7 @@ async def async_setup_entry( ) -class AtwWaterHeater(WaterHeaterDevice): +class AtwWaterHeater(WaterHeaterEntity): """Air-to-Water water heater.""" def __init__(self, api: MelCloudDevice, device: AtwDevice) -> None: diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index aeb2d1ee106..1c0d37c90df 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -4,7 +4,7 @@ import logging from homeassistant.components.water_heater import ( SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS @@ -98,7 +98,7 @@ def create_water_heater_entity(tado, name: str, zone_id: int, zone: str): return entity -class TadoWaterHeater(TadoZoneEntity, WaterHeaterDevice): +class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity): """Representation of a Tado water heater.""" def __init__( diff --git a/homeassistant/components/vicare/water_heater.py b/homeassistant/components/vicare/water_heater.py index eea3d81faf6..c6aa5205f24 100644 --- a/homeassistant/components/vicare/water_heater.py +++ b/homeassistant/components/vicare/water_heater.py @@ -5,7 +5,7 @@ import requests from homeassistant.components.water_heater import ( SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS @@ -60,7 +60,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) -class ViCareWater(WaterHeaterDevice): +class ViCareWater(WaterHeaterEntity): """Representation of the ViCare domestic hot water device.""" def __init__(self, name, api, heating_type): diff --git a/homeassistant/components/water_heater/__init__.py b/homeassistant/components/water_heater/__init__.py index 4de0a58a881..0763c552075 100644 --- a/homeassistant/components/water_heater/__init__.py +++ b/homeassistant/components/water_heater/__init__.py @@ -128,7 +128,7 @@ async def async_unload_entry(hass, entry): return await hass.data[DOMAIN].async_unload_entry(entry) -class WaterHeaterDevice(Entity): +class WaterHeaterEntity(Entity): """Representation of a water_heater device.""" @property @@ -319,3 +319,15 @@ async def async_service_temperature_set(entity, service): kwargs[value] = temp await entity.async_set_temperature(**kwargs) + + +class WaterHeaterDevice(WaterHeaterEntity): + """Representation of a water heater (for backwards compatibility).""" + + def __init_subclass__(cls, **kwargs): + """Print deprecation warning.""" + super().__init_subclass__(**kwargs) + _LOGGER.warning( + "WaterHeaterDevice is deprecated, modify %s to extend WaterHeaterEntity", + cls.__name__, + ) diff --git a/homeassistant/components/wink/water_heater.py b/homeassistant/components/wink/water_heater.py index dae6acf91bf..0ce31762c7a 100644 --- a/homeassistant/components/wink/water_heater.py +++ b/homeassistant/components/wink/water_heater.py @@ -14,7 +14,7 @@ from homeassistant.components.water_heater import ( SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - WaterHeaterDevice, + WaterHeaterEntity, ) from homeassistant.const import STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS @@ -51,7 +51,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([WinkWaterHeater(water_heater, hass)]) -class WinkWaterHeater(WinkDevice, WaterHeaterDevice): +class WinkWaterHeater(WinkDevice, WaterHeaterEntity): """Representation of a Wink water heater.""" @property diff --git a/tests/components/water_heater/test_init.py b/tests/components/water_heater/test_init.py new file mode 100644 index 00000000000..967e8b03620 --- /dev/null +++ b/tests/components/water_heater/test_init.py @@ -0,0 +1,12 @@ +"""Tests for Water heater.""" +from homeassistant.components import water_heater + + +def test_deprecated_base_class(caplog): + """Test deprecated base class.""" + + class CustomWaterHeater(water_heater.WaterHeaterDevice): + pass + + CustomWaterHeater() + assert "WaterHeaterDevice is deprecated, modify CustomWaterHeater" in caplog.text From 8c6506227150c74083b9827975ec19af20c68ff9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 1 May 2020 16:37:25 +0200 Subject: [PATCH 193/511] Several optimizations to automations (#35007) --- .../components/automation/__init__.py | 44 +++--- homeassistant/components/automation/config.py | 32 +++-- .../components/automation/litejet.py | 10 +- homeassistant/components/automation/zone.py | 5 +- tests/components/automation/test_event.py | 14 +- .../automation/test_geo_location.py | 22 +-- tests/components/automation/test_init.py | 24 ++-- tests/components/automation/test_mqtt.py | 6 +- .../automation/test_numeric_state.py | 128 ++++++++--------- tests/components/automation/test_state.py | 98 ++++++------- tests/components/automation/test_sun.py | 130 +++++++++--------- tests/components/automation/test_template.py | 84 +++++------ tests/components/automation/test_time.py | 24 ++-- .../automation/test_time_pattern.py | 22 +-- tests/components/automation/test_zone.py | 12 +- 15 files changed, 330 insertions(+), 325 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 3a5b3966d40..e5b66594d2f 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -1,4 +1,5 @@ """Allow to set up simple automation rules via the config file.""" +import asyncio import importlib import logging from typing import Any, Awaitable, Callable, List, Optional, Set @@ -127,13 +128,11 @@ def automations_with_entity(hass: HomeAssistant, entity_id: str) -> List[str]: component = hass.data[DOMAIN] - results = [] - - for automation_entity in component.entities: - if entity_id in automation_entity.referenced_entities: - results.append(automation_entity.entity_id) - - return results + return [ + automation_entity.entity_id + for automation_entity in component.entities + if entity_id in automation_entity.referenced_entities + ] @callback @@ -160,13 +159,11 @@ def automations_with_device(hass: HomeAssistant, device_id: str) -> List[str]: component = hass.data[DOMAIN] - results = [] - - for automation_entity in component.entities: - if device_id in automation_entity.referenced_devices: - results.append(automation_entity.entity_id) - - return results + return [ + automation_entity.entity_id + for automation_entity in component.entities + if device_id in automation_entity.referenced_devices + ] @callback @@ -443,26 +440,29 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self, home_assistant_start: bool ) -> Optional[Callable[[], None]]: """Set up the triggers.""" - removes = [] info = {"name": self._name, "home_assistant_start": home_assistant_start} + triggers = [] for conf in self._trigger_config: platform = importlib.import_module(f".{conf[CONF_PLATFORM]}", __name__) - remove = await platform.async_attach_trigger( # type: ignore - self.hass, conf, self.async_trigger, info + triggers.append( + platform.async_attach_trigger( # type: ignore + self.hass, conf, self.async_trigger, info + ) ) - if not remove: - _LOGGER.error("Error setting up trigger %s", self._name) - continue + results = await asyncio.gather(*triggers) - _LOGGER.info("Initialized trigger %s", self._name) - removes.append(remove) + if None in results: + _LOGGER.error("Error setting up trigger %s", self._name) + removes = [remove for remove in results if remove is not None] if not removes: return None + _LOGGER.info("Initialized trigger %s", self._name) + @callback def remove_triggers(): """Remove attached triggers.""" diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index d29a561f378..c2cd00fd683 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -36,17 +36,19 @@ async def async_validate_config_item(hass, config, full_config=None): config[CONF_TRIGGER] = triggers if CONF_CONDITION in config: - conditions = [] - for cond in config[CONF_CONDITION]: - cond = await condition.async_validate_condition_config(hass, cond) - conditions.append(cond) - config[CONF_CONDITION] = conditions + config[CONF_CONDITION] = await asyncio.gather( + *[ + condition.async_validate_condition_config(hass, cond) + for cond in config[CONF_CONDITION] + ] + ) - actions = [] - for action in config[CONF_ACTION]: - action = await script.async_validate_action_config(hass, action) - actions.append(action) - config[CONF_ACTION] = actions + config[CONF_ACTION] = await asyncio.gather( + *[ + script.async_validate_action_config(hass, action) + for action in config[CONF_ACTION] + ] + ) return config @@ -69,16 +71,18 @@ async def _try_async_validate_config_item(hass, config, full_config=None): async def async_validate_config(hass, config): """Validate config.""" - automations = [] validated_automations = await asyncio.gather( *( _try_async_validate_config_item(hass, p_config, config) for _, p_config in config_per_platform(config, DOMAIN) ) ) - for validated_automation in validated_automations: - if validated_automation is not None: - automations.append(validated_automation) + + automations = [ + validated_automation + for validated_automation in validated_automations + if validated_automation is not None + ] # Create a copy of the configuration with all config for current # component removed and add validated config back in. diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 12ffa29b962..5924cf3b809 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -85,9 +85,13 @@ async def async_attach_trigger(hass, config, action, automation_info): cancel_pressed_more_than() cancel_pressed_more_than = None held_time = dt_util.utcnow() - pressed_time - if held_less_than is not None and held_time < held_less_than: - if held_more_than is None or held_time > held_more_than: - hass.add_job(call_action) + + if ( + held_less_than is not None + and held_time < held_less_than + and (held_more_than is None or held_time > held_more_than) + ): + hass.add_job(call_action) hass.data["litejet_system"].on_switch_pressed(number, pressed) hass.data["litejet_system"].on_switch_released(number, released) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 14233d783f9..cae2a76dd03 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -47,10 +47,7 @@ async def async_attach_trigger(hass, config, action, automation_info): return zone_state = hass.states.get(zone_entity_id) - if from_s: - from_match = condition.zone(hass, zone_state, from_s) - else: - from_match = False + from_match = condition.zone(hass, zone_state, from_s) if from_s else False to_match = condition.zone(hass, zone_state, to_s) if ( diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 340bb6c1e95..cc9aecfdac4 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -46,7 +46,7 @@ async def test_if_fires_on_event(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_event_extra_data(hass, calls): @@ -64,14 +64,14 @@ async def test_if_fires_on_event_extra_data(hass, calls): hass.bus.async_fire("test_event", {"extra_key": "extra_data"}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 await common.async_turn_off(hass) await hass.async_block_till_done() hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_event_with_data(hass, calls): @@ -93,7 +93,7 @@ async def test_if_fires_on_event_with_data(hass, calls): hass.bus.async_fire("test_event", {"some_attr": "some_value", "another": "value"}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_event_with_empty_data_config(hass, calls): @@ -119,7 +119,7 @@ async def test_if_fires_on_event_with_empty_data_config(hass, calls): hass.bus.async_fire("test_event", {"some_attr": "some_value", "another": "value"}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_event_with_nested_data(hass, calls): @@ -143,7 +143,7 @@ async def test_if_fires_on_event_with_nested_data(hass, calls): "test_event", {"parent_attr": {"some_attr": "some_value", "another": "value"}} ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_if_event_data_not_matches(hass, calls): @@ -165,4 +165,4 @@ async def test_if_not_fires_if_event_data_not_matches(hass, calls): hass.bus.async_fire("test_event", {"some_attr": "some_other_value"}) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 diff --git a/tests/components/automation/test_geo_location.py b/tests/components/automation/test_geo_location.py index 5daca51d0a1..99ace50e77d 100644 --- a/tests/components/automation/test_geo_location.py +++ b/tests/components/automation/test_geo_location.py @@ -83,11 +83,11 @@ async def test_if_fires_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id assert ( - "geo_location - geo_location.entity - hello - hello - test" - == calls[0].data["some"] + calls[0].data["some"] + == "geo_location - geo_location.entity - hello - hello - test" ) # Set out of zone again so we can trigger call @@ -108,7 +108,7 @@ async def test_if_fires_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): @@ -143,7 +143,7 @@ async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_zone_leave(hass, calls): @@ -178,7 +178,7 @@ async def test_if_fires_on_zone_leave(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): @@ -213,7 +213,7 @@ async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_zone_appear(hass, calls): @@ -258,10 +258,10 @@ async def test_if_fires_on_zone_appear(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id assert ( - "geo_location - geo_location.entity - - hello - test" == calls[0].data["some"] + calls[0].data["some"] == "geo_location - geo_location.entity - - hello - test" ) @@ -308,7 +308,7 @@ async def test_if_fires_on_zone_disappear(hass, calls): hass.states.async_remove("geo_location.entity") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert ( - "geo_location - geo_location.entity - hello - - test" == calls[0].data["some"] + calls[0].data["some"] == "geo_location - geo_location.entity - hello - - test" ) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index cd4a01e9a28..a039604525f 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -148,7 +148,7 @@ async def test_service_specify_entity_id(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert ["hello.world"] == calls[0].data.get(ATTR_ENTITY_ID) @@ -170,7 +170,7 @@ async def test_service_specify_entity_id_list(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert ["hello.world", "hello.world2"] == calls[0].data.get(ATTR_ENTITY_ID) @@ -192,10 +192,10 @@ async def test_two_triggers(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_trigger_service_ignoring_condition(hass, calls): @@ -268,17 +268,17 @@ async def test_two_conditions_with_and(hass, calls): hass.states.async_set(entity_id, 100) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set(entity_id, 101) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set(entity_id, 151) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_automation_list_setting(hass, calls): @@ -302,11 +302,11 @@ async def test_automation_list_setting(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.bus.async_fire("test_event_2") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_automation_calling_two_actions(hass, calls): @@ -368,7 +368,7 @@ async def test_shared_context(hass, calls): assert event_mock.call_count == 2 # Verify automation triggered evenet for 'hello' automation - args, kwargs = event_mock.call_args_list[0] + args, _ = event_mock.call_args_list[0] first_trigger_context = args[0].context assert first_trigger_context.parent_id == context.id # Ensure event data has all attributes set @@ -376,7 +376,7 @@ async def test_shared_context(hass, calls): assert args[0].data.get(ATTR_ENTITY_ID) is not None # Ensure context set correctly for event fired by 'hello' automation - args, kwargs = first_automation_listener.call_args + args, _ = first_automation_listener.call_args assert args[0].context is first_trigger_context # Ensure the 'hello' automation state has the right context @@ -385,7 +385,7 @@ async def test_shared_context(hass, calls): assert state.context is first_trigger_context # Verify automation triggered evenet for 'bye' automation - args, kwargs = event_mock.call_args_list[1] + args, _ = event_mock.call_args_list[1] second_trigger_context = args[0].context assert second_trigger_context.parent_id == first_trigger_context.id # Ensure event data has all attributes set diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index b8c369f5e63..0a07c5aac48 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -57,7 +57,7 @@ async def test_if_fires_on_topic_match(hass, calls): await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic", "test_payload") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_topic_and_payload_match(hass, calls): @@ -79,7 +79,7 @@ async def test_if_fires_on_topic_and_payload_match(hass, calls): async_fire_mqtt_message(hass, "test-topic", "hello") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_topic_but_no_payload_match(hass, calls): @@ -101,7 +101,7 @@ async def test_if_not_fires_on_topic_but_no_payload_match(hass, calls): async_fire_mqtt_message(hass, "test-topic", "no-hello") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_encoding_default(hass, calls): diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index f779f022e65..1173de4b02a 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -52,7 +52,7 @@ async def test_if_fires_on_entity_change_below(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", 9, context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id # Set above 12 so the automation will fire again @@ -61,7 +61,7 @@ async def test_if_fires_on_entity_change_below(hass, calls): await hass.async_block_till_done() hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_over_to_below(hass, calls): @@ -87,7 +87,7 @@ async def test_if_fires_on_entity_change_over_to_below(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entities_change_over_to_below(hass, calls): @@ -114,10 +114,10 @@ async def test_if_fires_on_entities_change_over_to_below(hass, calls): # 9 is below 10 hass.states.async_set("test.entity_1", 9) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set("test.entity_2", 9) await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): @@ -144,18 +144,18 @@ async def test_if_not_fires_on_entity_change_below_to_below(hass, calls): # 9 is below 10 so this should fire hass.states.async_set("test.entity", 9, context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id # already below so should not fire again hass.states.async_set("test.entity", 5) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # still below so should not fire again hass.states.async_set("test.entity", 3) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): @@ -181,7 +181,7 @@ async def test_if_not_below_fires_on_entity_change_to_equal(hass, calls): # 10 is not below 10 so this should not fire again hass.states.async_set("test.entity", 10) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_initial_entity_below(hass, calls): @@ -207,7 +207,7 @@ async def test_if_fires_on_initial_entity_below(hass, calls): # Fire on first update even if initial state was already below hass.states.async_set("test.entity", 8) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_initial_entity_above(hass, calls): @@ -233,7 +233,7 @@ async def test_if_fires_on_initial_entity_above(hass, calls): # Fire on first update even if initial state was already above hass.states.async_set("test.entity", 12) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_above(hass, calls): @@ -255,7 +255,7 @@ async def test_if_fires_on_entity_change_above(hass, calls): # 11 is above 10 hass.states.async_set("test.entity", 11) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_below_to_above(hass, calls): @@ -282,7 +282,7 @@ async def test_if_fires_on_entity_change_below_to_above(hass, calls): # 11 is above 10 and 9 is below hass.states.async_set("test.entity", 11) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): @@ -309,12 +309,12 @@ async def test_if_not_fires_on_entity_change_above_to_above(hass, calls): # 12 is above 10 so this should fire hass.states.async_set("test.entity", 12) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # already above, should not fire again hass.states.async_set("test.entity", 15) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): @@ -341,7 +341,7 @@ async def test_if_not_above_fires_on_entity_change_to_equal(hass, calls): # 10 is not above 10 so this should not fire again hass.states.async_set("test.entity", 10) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_entity_change_below_range(hass, calls): @@ -364,7 +364,7 @@ async def test_if_fires_on_entity_change_below_range(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_below_above_range(hass, calls): @@ -387,7 +387,7 @@ async def test_if_fires_on_entity_change_below_above_range(hass, calls): # 4 is below 5 hass.states.async_set("test.entity", 4) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): @@ -414,7 +414,7 @@ async def test_if_fires_on_entity_change_over_to_below_range(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_over_to_below_above_range(hass, calls): @@ -441,7 +441,7 @@ async def test_if_fires_on_entity_change_over_to_below_above_range(hass, calls): # 4 is below 5 so it should not fire hass.states.async_set("test.entity", 4) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_if_entity_not_match(hass, calls): @@ -463,7 +463,7 @@ async def test_if_not_fires_if_entity_not_match(hass, calls): hass.states.async_set("test.entity", 11) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): @@ -485,7 +485,7 @@ async def test_if_fires_on_entity_change_below_with_attribute(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", 9, {"test_attribute": 11}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_entity_change_not_below_with_attribute(hass, calls): @@ -507,7 +507,7 @@ async def test_if_not_fires_on_entity_change_not_below_with_attribute(hass, call # 11 is not below 10 hass.states.async_set("test.entity", 11, {"test_attribute": 9}) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): @@ -530,7 +530,7 @@ async def test_if_fires_on_attribute_change_with_attribute_below(hass, calls): # 9 is below 10 hass.states.async_set("test.entity", "entity", {"test_attribute": 9}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_attribute_change_with_attribute_not_below(hass, calls): @@ -553,7 +553,7 @@ async def test_if_not_fires_on_attribute_change_with_attribute_not_below(hass, c # 11 is not below 10 hass.states.async_set("test.entity", "entity", {"test_attribute": 11}) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): @@ -576,7 +576,7 @@ async def test_if_not_fires_on_entity_change_with_attribute_below(hass, calls): # 11 is not below 10, entity state value should not be tested hass.states.async_set("test.entity", "9", {"test_attribute": 11}) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_entity_change_with_not_attribute_below(hass, calls): @@ -599,7 +599,7 @@ async def test_if_not_fires_on_entity_change_with_not_attribute_below(hass, call # 11 is not below 10, entity state value should not be tested hass.states.async_set("test.entity", "entity") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(hass, calls): @@ -624,7 +624,7 @@ async def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(hass, "test.entity", "entity", {"test_attribute": 9, "not_test_attribute": 11} ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_template_list(hass, calls): @@ -647,7 +647,7 @@ async def test_template_list(hass, calls): # 3 is below 10 hass.states.async_set("test.entity", "entity", {"test_attribute": [11, 15, 3]}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_template_string(hass, calls): @@ -686,10 +686,10 @@ async def test_template_string(hass, calls): await hass.async_block_till_done() hass.states.async_set("test.entity", "test state 2", {"test_attribute": "0.9"}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert ( - "numeric_state - test.entity - 10.0 - None - test state 1 - " - "test state 2" == calls[0].data["some"] + calls[0].data["some"] + == "numeric_state - test.entity - 10.0 - None - test state 1 - test state 2" ) @@ -715,7 +715,7 @@ async def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(hass, "test.entity", "entity", {"test_attribute": 11, "not_test_attribute": 9} ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_action(hass, calls): @@ -742,19 +742,19 @@ async def test_if_action(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set(entity_id, 8) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set(entity_id, 9) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_fails_setup_bad_for(hass, calls): @@ -826,7 +826,7 @@ async def test_if_not_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 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): @@ -853,7 +853,7 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(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 2 == len(calls) + assert len(calls) == 2 hass.states.async_set("test.entity_1", 15) hass.states.async_set("test.entity_2", 15) @@ -866,7 +866,7 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): @@ -897,11 +897,11 @@ async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.entity", 9, attributes={"mock_attr": "attr_change"}) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=4) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_for(hass, calls): @@ -927,7 +927,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_wait_template_with_trigger(hass, calls): @@ -965,7 +965,7 @@ async def test_wait_template_with_trigger(hass, calls): hass.states.async_set("test.entity", "8") await hass.async_block_till_done() await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert "numeric_state - test.entity - 12" == calls[0].data["some"] @@ -1000,16 +1000,16 @@ async def test_if_fires_on_entities_change_no_overlap(hass, calls): mock_utcnow.return_value += timedelta(seconds=10) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1" hass.states.async_set("test.entity_2", 9) await hass.async_block_till_done() mock_utcnow.return_value += timedelta(seconds=10) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2" async def test_if_fires_on_entities_change_overlap(hass, calls): @@ -1052,18 +1052,18 @@ async def test_if_fires_on_entities_change_overlap(hass, calls): async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.entity_2", 9) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1" mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2" async def test_if_fires_on_change_with_for_template_1(hass, calls): @@ -1087,10 +1087,10 @@ async def test_if_fires_on_change_with_for_template_1(hass, calls): hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_2(hass, calls): @@ -1114,10 +1114,10 @@ async def test_if_fires_on_change_with_for_template_2(hass, calls): hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_3(hass, calls): @@ -1141,10 +1141,10 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): hass.states.async_set("test.entity", 9) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_invalid_for_template(hass, calls): @@ -1215,22 +1215,22 @@ async def test_if_fires_on_entities_change_overlap_for_template(hass, calls): async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.entity_2", 9) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1 - 0:00:05" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1 - 0:00:05" mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 mock_utcnow.return_value += timedelta(seconds=5) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2 - 0:00:10" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2 - 0:00:10" def test_below_above(): diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 949851f5470..033ce44e5a4 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -65,15 +65,15 @@ async def test_if_fires_on_entity_change(hass, calls): hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "state - test.entity - hello - world - None" == calls[0].data["some"] + assert calls[0].data["some"] == "state - test.entity - hello - world - None" await common.async_turn_off(hass) await hass.async_block_till_done() hass.states.async_set("test.entity", "planet") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_from_filter(hass, calls): @@ -96,7 +96,7 @@ async def test_if_fires_on_entity_change_with_from_filter(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_to_filter(hass, calls): @@ -119,7 +119,7 @@ async def test_if_fires_on_entity_change_with_to_filter(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_attribute_change_with_to_filter(hass, calls): @@ -143,7 +143,7 @@ async def test_if_fires_on_attribute_change_with_to_filter(hass, calls): hass.states.async_set("test.entity", "world", {"test_attribute": 11}) hass.states.async_set("test.entity", "world", {"test_attribute": 12}) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_both_filters(hass, calls): @@ -167,7 +167,7 @@ async def test_if_fires_on_entity_change_with_both_filters(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_if_to_filter_not_match(hass, calls): @@ -191,7 +191,7 @@ async def test_if_not_fires_if_to_filter_not_match(hass, calls): hass.states.async_set("test.entity", "moon") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_if_from_filter_not_match(hass, calls): @@ -217,7 +217,7 @@ async def test_if_not_fires_if_from_filter_not_match(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_if_entity_not_match(hass, calls): @@ -236,7 +236,7 @@ async def test_if_not_fires_if_entity_not_match(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_action(hass, calls): @@ -262,13 +262,13 @@ async def test_if_action(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 hass.states.async_set(entity_id, test_state + "something") hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fails_setup_if_to_boolean_value(hass, calls): @@ -377,7 +377,7 @@ async def test_if_not_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 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): @@ -404,7 +404,7 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(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 2 == len(calls) + assert len(calls) == 2 hass.states.async_set("test.entity_1", "world_no") hass.states.async_set("test.entity_2", "world_no") @@ -417,7 +417,7 @@ async def test_if_not_fires_on_entities_change_with_for_after_stop(hass, calls): async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10)) await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): @@ -450,11 +450,11 @@ async def test_if_fires_on_entity_change_with_for_attribute_change(hass, calls): "test.entity", "world", attributes={"mock_attr": "attr_change"} ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=4) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_for_multiple_force_update(hass, calls): @@ -481,16 +481,16 @@ async def test_if_fires_on_entity_change_with_for_multiple_force_update(hass, ca mock_utcnow.return_value = utcnow hass.states.async_set("test.force_entity", "world", None, True) await hass.async_block_till_done() - for _ in range(0, 4): + for _ in range(4): mock_utcnow.return_value += timedelta(seconds=1) async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.force_entity", "world", None, True) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=4) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_entity_change_with_for(hass, calls): @@ -539,7 +539,7 @@ async def test_if_fires_on_entity_removal(hass, calls): assert hass.states.async_remove("test.entity", context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id @@ -571,13 +571,13 @@ async def test_if_fires_on_for_condition(hass, calls): # not enough time has passed hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Time travel 10 secs into the future mock_utcnow.return_value = point2 hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_for_condition_attribute_change(hass, calls): @@ -609,7 +609,7 @@ async def test_if_fires_on_for_condition_attribute_change(hass, calls): # not enough time has passed hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Still not enough time has passed, but an attribute is changed mock_utcnow.return_value = point2 @@ -618,13 +618,13 @@ async def test_if_fires_on_for_condition_attribute_change(hass, calls): ) hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Enough time has now passed mock_utcnow.return_value = point3 hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fails_setup_for_without_time(hass, calls): @@ -707,8 +707,8 @@ async def test_wait_template_with_trigger(hass, calls): await hass.async_block_till_done() hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() - assert 1 == len(calls) - assert "state - test.entity - hello - world" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "state - test.entity - hello - world" async def test_if_fires_on_entities_change_no_overlap(hass, calls): @@ -741,16 +741,16 @@ async def test_if_fires_on_entities_change_no_overlap(hass, calls): mock_utcnow.return_value += timedelta(seconds=10) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1" hass.states.async_set("test.entity_2", "world") await hass.async_block_till_done() mock_utcnow.return_value += timedelta(seconds=10) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2" async def test_if_fires_on_entities_change_overlap(hass, calls): @@ -792,18 +792,18 @@ async def test_if_fires_on_entities_change_overlap(hass, calls): async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.entity_2", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1" mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2" async def test_if_fires_on_change_with_for_template_1(hass, calls): @@ -826,10 +826,10 @@ async def test_if_fires_on_change_with_for_template_1(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_2(hass, calls): @@ -852,10 +852,10 @@ async def test_if_fires_on_change_with_for_template_2(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_3(hass, calls): @@ -878,10 +878,10 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_invalid_for_template_1(hass, calls): @@ -950,19 +950,19 @@ async def test_if_fires_on_entities_change_overlap_for_template(hass, calls): async_fire_time_changed(hass, mock_utcnow.return_value) hass.states.async_set("test.entity_2", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) - assert "test.entity_1 - 0:00:05" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "test.entity_1 - 0:00:05" mock_utcnow.return_value += timedelta(seconds=3) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 mock_utcnow.return_value += timedelta(seconds=5) async_fire_time_changed(hass, mock_utcnow.return_value) await hass.async_block_till_done() - assert 2 == len(calls) - assert "test.entity_2 - 0:00:10" == calls[1].data["some"] + assert len(calls) == 2 + assert calls[1].data["some"] == "test.entity_2 - 0:00:10" diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 3468c9e9480..4cb2672ab64 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -59,7 +59,7 @@ async def test_sunset_trigger(hass, calls): async_fire_time_changed(hass, trigger_time) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 with patch("homeassistant.util.dt.utcnow", return_value=now): await common.async_turn_on(hass) @@ -67,7 +67,7 @@ async def test_sunset_trigger(hass, calls): async_fire_time_changed(hass, trigger_time) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_sunrise_trigger(hass, calls): @@ -89,7 +89,7 @@ async def test_sunrise_trigger(hass, calls): async_fire_time_changed(hass, trigger_time) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_sunset_trigger_with_offset(hass, calls): @@ -121,8 +121,8 @@ async def test_sunset_trigger_with_offset(hass, calls): async_fire_time_changed(hass, trigger_time) await hass.async_block_till_done() - assert 1 == len(calls) - assert "sun - sunset - 0:30:00" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "sun - sunset - 0:30:00" async def test_sunrise_trigger_with_offset(hass, calls): @@ -148,7 +148,7 @@ async def test_sunrise_trigger_with_offset(hass, calls): async_fire_time_changed(hass, trigger_time) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_action_before_sunrise_no_offset(hass, calls): @@ -176,28 +176,28 @@ async def test_if_action_before_sunrise_no_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise -> 'before sunrise' true now = datetime(2015, 9, 16, 13, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'before sunrise' true now = datetime(2015, 9, 16, 7, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = local midnight - 1s -> 'before sunrise' not true now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_after_sunrise_no_offset(hass, calls): @@ -225,28 +225,28 @@ async def test_if_action_after_sunrise_no_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise + 1s -> 'after sunrise' true now = datetime(2015, 9, 16, 13, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'after sunrise' not true now = datetime(2015, 9, 16, 7, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight - 1s -> 'after sunrise' true now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_before_sunrise_with_offset(hass, calls): @@ -278,56 +278,56 @@ async def test_if_action_before_sunrise_with_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise + 1h -> 'before sunrise' with offset +1h true now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = UTC midnight -> 'before sunrise' with offset +1h not true now = datetime(2015, 9, 17, 0, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = UTC midnight - 1s -> 'before sunrise' with offset +1h not true now = datetime(2015, 9, 16, 23, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'before sunrise' with offset +1h true now = datetime(2015, 9, 16, 7, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = local midnight - 1s -> 'before sunrise' with offset +1h not true now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = sunset -> 'before sunrise' with offset +1h not true now = datetime(2015, 9, 17, 1, 56, 48, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = sunset -1s -> 'before sunrise' with offset +1h not true now = datetime(2015, 9, 17, 1, 56, 45, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_before_sunset_with_offset(hass, calls): @@ -359,56 +359,56 @@ async def test_if_action_before_sunset_with_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = sunset + 1s + 1h -> 'before sunset' with offset +1h not true now = datetime(2015, 9, 17, 2, 55, 25, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = sunset + 1h -> 'before sunset' with offset +1h true now = datetime(2015, 9, 17, 2, 55, 24, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = UTC midnight -> 'before sunset' with offset +1h true now = datetime(2015, 9, 17, 0, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 3 == len(calls) + assert len(calls) == 3 # now = UTC midnight - 1s -> 'before sunset' with offset +1h true now = datetime(2015, 9, 16, 23, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 4 == len(calls) + assert len(calls) == 4 # now = sunrise -> 'before sunset' with offset +1h true now = datetime(2015, 9, 16, 13, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 5 == len(calls) + assert len(calls) == 5 # now = sunrise -1s -> 'before sunset' with offset +1h true now = datetime(2015, 9, 16, 13, 32, 42, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 6 == len(calls) + assert len(calls) == 6 # now = local midnight-1s -> 'after sunrise' with offset +1h not true now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 6 == len(calls) + assert len(calls) == 6 async def test_if_action_after_sunrise_with_offset(hass, calls): @@ -440,70 +440,70 @@ async def test_if_action_after_sunrise_with_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise + 1h -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 16, 14, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = UTC noon -> 'after sunrise' with offset +1h not true now = datetime(2015, 9, 16, 12, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = UTC noon - 1s -> 'after sunrise' with offset +1h not true now = datetime(2015, 9, 16, 11, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local noon -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 16, 19, 1, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = local noon - 1s -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 16, 18, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 3 == len(calls) + assert len(calls) == 3 # now = sunset -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 17, 1, 55, 24, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 4 == len(calls) + assert len(calls) == 4 # now = sunset + 1s -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 17, 1, 55, 25, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 5 == len(calls) + assert len(calls) == 5 # now = local midnight-1s -> 'after sunrise' with offset +1h true now = datetime(2015, 9, 17, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 6 == len(calls) + assert len(calls) == 6 # now = local midnight -> 'after sunrise' with offset +1h not true now = datetime(2015, 9, 17, 7, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 6 == len(calls) + assert len(calls) == 6 async def test_if_action_after_sunset_with_offset(hass, calls): @@ -535,28 +535,28 @@ async def test_if_action_after_sunset_with_offset(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunset + 1h -> 'after sunset' with offset +1h true now = datetime(2015, 9, 16, 2, 56, 46, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = midnight-1s -> 'after sunset' with offset +1h true now = datetime(2015, 9, 16, 6, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = midnight -> 'after sunset' with offset +1h not true now = datetime(2015, 9, 16, 7, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_before_and_after_during(hass, calls): @@ -588,35 +588,35 @@ async def test_if_action_before_and_after_during(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunset + 1s -> 'after sunrise' + 'before sunset' not true now = datetime(2015, 9, 17, 1, 55, 25, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise -> 'after sunrise' + 'before sunset' true now = datetime(2015, 9, 16, 13, 32, 43, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = sunset -> 'after sunrise' + 'before sunset' true now = datetime(2015, 9, 17, 1, 55, 24, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = 9AM local -> 'after sunrise' + 'before sunset' true now = datetime(2015, 9, 16, 16, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 3 == len(calls) + assert len(calls) == 3 async def test_if_action_before_sunrise_no_offset_kotzebue(hass, calls): @@ -651,28 +651,28 @@ async def test_if_action_before_sunrise_no_offset_kotzebue(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise -> 'before sunrise' true now = datetime(2015, 7, 24, 15, 17, 24, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'before sunrise' true now = datetime(2015, 7, 24, 8, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = local midnight - 1s -> 'before sunrise' not true now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_after_sunrise_no_offset_kotzebue(hass, calls): @@ -707,28 +707,28 @@ async def test_if_action_after_sunrise_no_offset_kotzebue(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = sunrise - 1s -> 'after sunrise' not true now = datetime(2015, 7, 24, 15, 17, 23, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'after sunrise' not true now = datetime(2015, 7, 24, 8, 0, 1, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight - 1s -> 'after sunrise' true now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_before_sunset_no_offset_kotzebue(hass, calls): @@ -763,28 +763,28 @@ async def test_if_action_before_sunset_no_offset_kotzebue(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # now = sunrise -> 'before sunrise' true now = datetime(2015, 7, 25, 11, 16, 27, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'before sunrise' true now = datetime(2015, 7, 24, 8, 0, 0, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 # now = local midnight - 1s -> 'before sunrise' not true now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 async def test_if_action_after_sunset_no_offset_kotzebue(hass, calls): @@ -819,25 +819,25 @@ async def test_if_action_after_sunset_no_offset_kotzebue(hass, calls): with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = sunset - 1s -> 'after sunset' not true now = datetime(2015, 7, 25, 11, 16, 26, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight -> 'after sunset' not true now = datetime(2015, 7, 24, 8, 0, 1, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # now = local midnight - 1s -> 'after sunset' true now = datetime(2015, 7, 24, 7, 59, 59, tzinfo=dt_util.UTC) with patch("homeassistant.util.dt.utcnow", return_value=now): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index 27e0d4f6965..91ecc4ad4ac 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -46,14 +46,14 @@ async def test_if_fires_on_change_bool(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 await common.async_turn_off(hass) await hass.async_block_till_done() hass.states.async_set("test.entity", "planet") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_change_str(hass, calls): @@ -71,7 +71,7 @@ async def test_if_fires_on_change_str(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_change_str_crazy(hass, calls): @@ -89,7 +89,7 @@ async def test_if_fires_on_change_str_crazy(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_change_bool(hass, calls): @@ -107,7 +107,7 @@ async def test_if_not_fires_on_change_bool(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_change_str(hass, calls): @@ -125,7 +125,7 @@ async def test_if_not_fires_on_change_str(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_on_change_str_crazy(hass, calls): @@ -146,7 +146,7 @@ async def test_if_not_fires_on_change_str_crazy(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_no_change(hass, calls): @@ -186,12 +186,12 @@ async def test_if_fires_on_two_change(hass, calls): # Trigger once hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 # Trigger again hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_change_with_template(hass, calls): @@ -212,7 +212,7 @@ async def test_if_fires_on_change_with_template(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_on_change_with_template(hass, calls): @@ -273,7 +273,7 @@ async def test_if_fires_on_change_with_template_advanced(hass, calls): hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id assert "template - test.entity - hello - world - None" == calls[0].data["some"] @@ -301,12 +301,12 @@ async def test_if_fires_on_no_change_with_template_advanced(hass, calls): # Different state hass.states.async_set("test.entity", "worldz") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Different state hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_change_with_template_2(hass, calls): @@ -374,17 +374,17 @@ async def test_if_action(hass, calls): # Condition is not true yet hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Change condition to true, but it shouldn't be triggered yet hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 # Condition is true and event is triggered hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_change_with_bad_template(hass, calls): @@ -420,7 +420,7 @@ async def test_if_fires_on_change_with_bad_template_2(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_wait_template_with_trigger(hass, calls): @@ -462,8 +462,8 @@ async def test_wait_template_with_trigger(hass, calls): await hass.async_block_till_done() hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() - assert 1 == len(calls) - assert "template - test.entity - hello - world - None" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "template - test.entity - hello - world - None" async def test_if_fires_on_change_with_for(hass, calls): @@ -485,10 +485,10 @@ async def test_if_fires_on_change_with_for(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_advanced(hass, calls): @@ -527,10 +527,10 @@ async def test_if_fires_on_change_with_for_advanced(hass, calls): hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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 assert calls[0].context.parent_id == context.id assert "template - test.entity - hello - world - 0:00:05" == calls[0].data["some"] @@ -554,7 +554,7 @@ async def test_if_fires_on_change_with_for_0(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_on_change_with_for_0_advanced(hass, calls): @@ -593,9 +593,9 @@ async def test_if_fires_on_change_with_for_0_advanced(hass, calls): hass.states.async_set("test.entity", "world", context=context) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id - assert "template - test.entity - hello - world - 0:00:00" == calls[0].data["some"] + assert calls[0].data["some"] == "template - test.entity - hello - world - 0:00:00" async def test_if_fires_on_change_with_for_2(hass, calls): @@ -617,10 +617,10 @@ async def test_if_fires_on_change_with_for_2(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_not_fires_on_change_with_for(hass, calls): @@ -642,16 +642,16 @@ async def test_if_not_fires_on_change_with_for(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=4)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 hass.states.async_set("test.entity", "hello") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=6)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_not_fires_when_turned_off_with_for(hass, calls): @@ -673,16 +673,16 @@ async def test_if_not_fires_when_turned_off_with_for(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=4)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 await common.async_turn_off(hass) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=6)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_change_with_for_template_1(hass, calls): @@ -704,10 +704,10 @@ async def test_if_fires_on_change_with_for_template_1(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_2(hass, calls): @@ -729,10 +729,10 @@ async def test_if_fires_on_change_with_for_template_2(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_change_with_for_template_3(hass, calls): @@ -754,10 +754,10 @@ async def test_if_fires_on_change_with_for_template_3(hass, calls): hass.states.async_set("test.entity", "world") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 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_invalid_for_template_1(hass, calls): diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 511f8a305e6..ec8d504652b 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -49,8 +49,8 @@ async def test_if_fires_using_at(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=5, minute=0, second=0)) await hass.async_block_till_done() - assert 1 == len(calls) - assert "time - 5" == calls[0].data["some"] + assert len(calls) == 1 + assert calls[0].data["some"] == "time - 5" async def test_if_not_fires_using_wrong_at(hass, calls): @@ -77,7 +77,7 @@ async def test_if_not_fires_using_wrong_at(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=0, second=5)) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_action_before(hass, calls): @@ -101,13 +101,13 @@ async def test_if_action_before(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 with patch("homeassistant.helpers.condition.dt_util.now", return_value=after_10): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_action_after(hass, calls): @@ -131,13 +131,13 @@ async def test_if_action_after(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 with patch("homeassistant.helpers.condition.dt_util.now", return_value=after_10): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_action_one_weekday(hass, calls): @@ -162,13 +162,13 @@ async def test_if_action_one_weekday(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 with patch("homeassistant.helpers.condition.dt_util.now", return_value=tuesday): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_action_list_weekday(hass, calls): @@ -194,16 +194,16 @@ async def test_if_action_list_weekday(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 with patch("homeassistant.helpers.condition.dt_util.now", return_value=tuesday): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 with patch("homeassistant.helpers.condition.dt_util.now", return_value=wednesday): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 diff --git a/tests/components/automation/test_time_pattern.py b/tests/components/automation/test_time_pattern.py index 2c0574c3238..01aa32f318f 100644 --- a/tests/components/automation/test_time_pattern.py +++ b/tests/components/automation/test_time_pattern.py @@ -41,14 +41,14 @@ async def test_if_fires_when_hour_matches(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 await common.async_turn_off(hass) await hass.async_block_till_done() async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_when_minute_matches(hass, calls): @@ -72,7 +72,7 @@ async def test_if_fires_when_minute_matches(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(minute=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_when_second_matches(hass, calls): @@ -96,7 +96,7 @@ async def test_if_fires_when_second_matches(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(second=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_when_all_matches(hass, calls): @@ -120,7 +120,7 @@ async def test_if_fires_when_all_matches(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=3)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_periodic_seconds(hass, calls): @@ -144,7 +144,7 @@ async def test_if_fires_periodic_seconds(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0, minute=0, second=2)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_periodic_minutes(hass, calls): @@ -168,7 +168,7 @@ async def test_if_fires_periodic_minutes(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=0, minute=2, second=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_fires_periodic_hours(hass, calls): @@ -192,7 +192,7 @@ async def test_if_fires_periodic_hours(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=2, minute=0, second=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_default_values(hass, calls): @@ -211,14 +211,14 @@ async def test_default_values(hass, calls): async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=0)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async_fire_time_changed(hass, dt_util.utcnow().replace(hour=1, minute=2, second=1)) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async_fire_time_changed(hass, dt_util.utcnow().replace(hour=2, minute=2, second=0)) await hass.async_block_till_done() - assert 2 == len(calls) + assert len(calls) == 2 diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index cb031486b6f..e80f70b10fe 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -81,7 +81,7 @@ async def test_if_fires_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 assert calls[0].context.parent_id == context.id assert "zone - test.entity - hello - hello - test" == calls[0].data["some"] @@ -99,7 +99,7 @@ async def test_if_fires_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): @@ -130,7 +130,7 @@ async def test_if_not_fires_for_enter_on_zone_leave(hass, calls): ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_if_fires_on_zone_leave(hass, calls): @@ -161,7 +161,7 @@ async def test_if_fires_on_zone_leave(hass, calls): ) await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): @@ -192,7 +192,7 @@ async def test_if_not_fires_for_leave_on_zone_enter(hass, calls): ) await hass.async_block_till_done() - assert 0 == len(calls) + assert len(calls) == 0 async def test_zone_condition(hass, calls): @@ -220,4 +220,4 @@ async def test_zone_condition(hass, calls): hass.bus.async_fire("test_event") await hass.async_block_till_done() - assert 1 == len(calls) + assert len(calls) == 1 From ea70d71e8f9c52421be91723ddd94cd50a17b7c0 Mon Sep 17 00:00:00 2001 From: Markus Breitenberger Date: Fri, 1 May 2020 16:46:36 +0200 Subject: [PATCH 194/511] Use pulsectl library for PulseAudio connection (#34965) Get rid of internal library code and use pulsectl library to communicate with PulseAudio server. This is a breaking change as the library uses the much more powerful native interface instead of the CLI interface, requiring the need to change the default port. On the bright side, this also solves some issues with the existing implementation: - There was no test if the complete list of loaded modules was already received. If not all data could be read at once, the remaining modules not yet in the buffer were considered absent, resulting in unreliable behavior when a lot of modules were loaded on the server. - A switch could be turned on before the list of loaded modules was loaded, leading to a loopback module being loaded even though this module was already active (#32016). --- .../pulseaudio_loopback/manifest.json | 1 + .../components/pulseaudio_loopback/switch.py | 167 ++++++------------ requirements_all.txt | 3 + 3 files changed, 55 insertions(+), 116 deletions(-) diff --git a/homeassistant/components/pulseaudio_loopback/manifest.json b/homeassistant/components/pulseaudio_loopback/manifest.json index 8775f5f0947..bc38d8c2594 100644 --- a/homeassistant/components/pulseaudio_loopback/manifest.json +++ b/homeassistant/components/pulseaudio_loopback/manifest.json @@ -2,5 +2,6 @@ "domain": "pulseaudio_loopback", "name": "PulseAudio Loopback", "documentation": "https://www.home-assistant.io/integrations/pulseaudio_loopback", + "requirements": ["pulsectl==20.2.4"], "codeowners": [] } diff --git a/homeassistant/components/pulseaudio_loopback/switch.py b/homeassistant/components/pulseaudio_loopback/switch.py index 45577fcd674..9c27ab4e027 100644 --- a/homeassistant/components/pulseaudio_loopback/switch.py +++ b/homeassistant/components/pulseaudio_loopback/switch.py @@ -1,52 +1,32 @@ """Switch logic for loading/unloading pulseaudio loopback modules.""" -from datetime import timedelta import logging -import re -import socket +from pulsectl import Pulse, PulseError import voluptuous as vol -from homeassistant import util from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(__name__) -_PULSEAUDIO_SERVERS = {} +DOMAIN = "pulseaudio_loopback" + +_LOGGER = logging.getLogger(__name__) -CONF_BUFFER_SIZE = "buffer_size" CONF_SINK_NAME = "sink_name" CONF_SOURCE_NAME = "source_name" -CONF_TCP_TIMEOUT = "tcp_timeout" -DEFAULT_BUFFER_SIZE = 1024 -DEFAULT_HOST = "localhost" DEFAULT_NAME = "paloopback" -DEFAULT_PORT = 4712 -DEFAULT_TCP_TIMEOUT = 3 +DEFAULT_PORT = 4713 IGNORED_SWITCH_WARN = "Switch is already in the desired state. Ignoring." -LOAD_CMD = "load-module module-loopback sink={0} source={1}" - -MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) -MOD_REGEX = ( - r"index: ([0-9]+)\s+name: " - r"\s+argument: (?=<.*sink={0}.*>)(?=<.*source={1}.*>)" -) - -UNLOAD_CMD = "unload-module {0}" - PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_SINK_NAME): cv.string, vol.Required(CONF_SOURCE_NAME): cv.string, - vol.Optional(CONF_BUFFER_SIZE, default=DEFAULT_BUFFER_SIZE): cv.positive_int, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_HOST): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_TCP_TIMEOUT, default=DEFAULT_TCP_TIMEOUT): cv.positive_int, } ) @@ -58,97 +38,62 @@ def setup_platform(hass, config, add_entities, discovery_info=None): source_name = config.get(CONF_SOURCE_NAME) host = config.get(CONF_HOST) port = config.get(CONF_PORT) - buffer_size = config.get(CONF_BUFFER_SIZE) - tcp_timeout = config.get(CONF_TCP_TIMEOUT) + + hass.data.setdefault(DOMAIN, {}) server_id = str.format("{0}:{1}", host, port) - if server_id in _PULSEAUDIO_SERVERS: - server = _PULSEAUDIO_SERVERS[server_id] + if host: + connect_to_server = server_id else: - server = PAServer(host, port, buffer_size, tcp_timeout) - _PULSEAUDIO_SERVERS[server_id] = server + connect_to_server = None - add_entities([PALoopbackSwitch(hass, name, server, sink_name, source_name)]) + if server_id in hass.data[DOMAIN]: + server = hass.data[DOMAIN][server_id] + else: + server = Pulse(server=connect_to_server, connect=False, threading_lock=True) + hass.data[DOMAIN][server_id] = server - -class PAServer: - """Representation of a Pulseaudio server.""" - - _current_module_state = "" - - def __init__(self, host, port, buff_sz, tcp_timeout): - """Initialize PulseAudio server.""" - self._pa_host = host - self._pa_port = int(port) - self._buffer_size = int(buff_sz) - self._tcp_timeout = int(tcp_timeout) - - def _send_command(self, cmd, response_expected): - """Send a command to the pa server using a socket.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(self._tcp_timeout) - try: - sock.connect((self._pa_host, self._pa_port)) - _LOGGER.info("Calling pulseaudio: %s", cmd) - sock.send((cmd + "\n").encode("utf-8")) - if response_expected: - return_data = self._get_full_response(sock) - _LOGGER.debug("Data received from pulseaudio: %s", return_data) - else: - return_data = "" - finally: - sock.close() - return return_data - - def _get_full_response(self, sock): - """Get the full response back from pulseaudio.""" - result = "" - rcv_buffer = sock.recv(self._buffer_size) - result += rcv_buffer.decode("utf-8") - - while len(rcv_buffer) == self._buffer_size: - rcv_buffer = sock.recv(self._buffer_size) - result += rcv_buffer.decode("utf-8") - - return result - - @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) - def update_module_state(self): - """Refresh state in case an alternate process modified this data.""" - self._current_module_state = self._send_command("list-modules", True) - - def turn_on(self, sink_name, source_name): - """Send a command to pulseaudio to turn on the loopback.""" - self._send_command(str.format(LOAD_CMD, sink_name, source_name), False) - - def turn_off(self, module_idx): - """Send a command to pulseaudio to turn off the loopback.""" - self._send_command(str.format(UNLOAD_CMD, module_idx), False) - - def get_module_idx(self, sink_name, source_name): - """For a sink/source, return its module id in our cache, if found.""" - result = re.search( - str.format(MOD_REGEX, re.escape(sink_name), re.escape(source_name)), - self._current_module_state, - ) - if result and result.group(1).isdigit(): - return int(result.group(1)) - return -1 + add_entities([PALoopbackSwitch(name, server, sink_name, source_name)], True) class PALoopbackSwitch(SwitchEntity): """Representation the presence or absence of a PA loopback module.""" - def __init__(self, hass, name, pa_server, sink_name, source_name): + def __init__(self, name, pa_server, sink_name, source_name): """Initialize the Pulseaudio switch.""" - self._module_idx = -1 - self._hass = hass + self._module_idx = None self._name = name self._sink_name = sink_name self._source_name = source_name self._pa_svr = pa_server + def _get_module_idx(self): + try: + self._pa_svr.connect() + + for module in self._pa_svr.module_list(): + if not module.name == "module-loopback": + continue + + if f"sink={self._sink_name}" not in module.argument: + continue + + if f"source={self._source_name}" not in module.argument: + continue + + return module.index + + except PulseError: + return None + + return None + + @property + def available(self): + """Return true when connected to server.""" + return self._pa_svr.connected + @property def name(self): """Return the name of the switch.""" @@ -157,35 +102,25 @@ class PALoopbackSwitch(SwitchEntity): @property def is_on(self): """Return true if device is on.""" - return self._module_idx > 0 + return self._module_idx is not None def turn_on(self, **kwargs): """Turn the device on.""" if not self.is_on: - self._pa_svr.turn_on(self._sink_name, self._source_name) - self._pa_svr.update_module_state(no_throttle=True) - self._module_idx = self._pa_svr.get_module_idx( - self._sink_name, self._source_name + self._pa_svr.module_load( + "module-loopback", + args=f"sink={self._sink_name} source={self._source_name}", ) - self.schedule_update_ha_state() else: _LOGGER.warning(IGNORED_SWITCH_WARN) def turn_off(self, **kwargs): """Turn the device off.""" if self.is_on: - self._pa_svr.turn_off(self._module_idx) - self._pa_svr.update_module_state(no_throttle=True) - self._module_idx = self._pa_svr.get_module_idx( - self._sink_name, self._source_name - ) - self.schedule_update_ha_state() + self._pa_svr.module_unload(self._module_idx) else: _LOGGER.warning(IGNORED_SWITCH_WARN) def update(self): """Refresh state in case an alternate process modified this data.""" - self._pa_svr.update_module_state() - self._module_idx = self._pa_svr.get_module_idx( - self._sink_name, self._source_name - ) + self._module_idx = self._get_module_idx() diff --git a/requirements_all.txt b/requirements_all.txt index 62a27f90c38..418bf0bdb9f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1105,6 +1105,9 @@ ptvsd==4.2.8 # homeassistant.components.wink pubnubsub-handler==1.0.8 +# homeassistant.components.pulseaudio_loopback +pulsectl==20.2.4 + # homeassistant.components.androidtv pure-python-adb==0.2.2.dev0 From 2b13a8cde4514187daffaf614e1d0405d7e39436 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 1 May 2020 17:41:57 +0200 Subject: [PATCH 195/511] Include QoS and retain in MQTT debug info (#35011) --- homeassistant/components/mqtt/debug_info.py | 8 +- tests/components/mqtt/test_common.py | 18 ++++- tests/components/mqtt/test_init.py | 88 ++++++++++++++++++++- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/debug_info.py b/homeassistant/components/mqtt/debug_info.py index 86850c61638..75e4b53a191 100644 --- a/homeassistant/components/mqtt/debug_info.py +++ b/homeassistant/components/mqtt/debug_info.py @@ -137,7 +137,13 @@ async def info_for_device(hass, device_id): { "topic": topic, "messages": [ - {"payload": msg.payload, "time": msg.timestamp, "topic": msg.topic} + { + "payload": msg.payload, + "qos": msg.qos, + "retain": msg.retain, + "time": msg.timestamp, + "topic": msg.topic, + } for msg in list(subscription["messages"]) ], } diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 1199aaa40c7..949d77c244d 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -601,7 +601,13 @@ async def help_test_entity_debug_info_max_messages(hass, mqtt_mock, domain, conf == debug_info.STORED_MESSAGES ) messages = [ - {"topic": "test-topic", "payload": f"{i}", "time": start_dt} + { + "payload": f"{i}", + "qos": 0, + "retain": False, + "time": start_dt, + "topic": "test-topic", + } for i in range(1, debug_info.STORED_MESSAGES + 1) ] assert {"topic": "test-topic", "messages": messages} in debug_info_data["entities"][ @@ -656,7 +662,15 @@ async def help_test_entity_debug_info_message( assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert { "topic": topic, - "messages": [{"topic": topic, "payload": payload, "time": start_dt}], + "messages": [ + { + "payload": payload, + "qos": 0, + "retain": False, + "time": start_dt, + "topic": topic, + } + ], } in debug_info_data["entities"][0]["subscriptions"] diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 672ff127b4d..28cca6a856a 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -1291,7 +1291,15 @@ async def test_debug_info_wildcard(hass, mqtt_mock): assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 assert { "topic": "sensor/#", - "messages": [{"topic": "sensor/abc", "payload": "123", "time": start_dt}], + "messages": [ + { + "payload": "123", + "qos": 0, + "retain": False, + "time": start_dt, + "topic": "sensor/abc", + } + ], } in debug_info_data["entities"][0]["subscriptions"] @@ -1338,8 +1346,20 @@ async def test_debug_info_filter_same(hass, mqtt_mock): assert { "topic": "sensor/#", "messages": [ - {"payload": "123", "time": dt1, "topic": "sensor/abc"}, - {"payload": "123", "time": dt2, "topic": "sensor/abc"}, + { + "payload": "123", + "qos": 0, + "retain": False, + "time": dt1, + "topic": "sensor/abc", + }, + { + "payload": "123", + "qos": 0, + "retain": False, + "time": dt2, + "topic": "sensor/abc", + }, ], } == debug_info_data["entities"][0]["subscriptions"][0] @@ -1382,6 +1402,8 @@ async def test_debug_info_same_topic(hass, mqtt_mock): assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert { "payload": "123", + "qos": 0, + "retain": False, "time": start_dt, "topic": "sensor/status", } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] @@ -1395,3 +1417,63 @@ async def test_debug_info_same_topic(hass, mqtt_mock): with patch("homeassistant.util.dt.utcnow") as dt_utcnow: dt_utcnow.return_value = start_dt async_fire_mqtt_message(hass, "sensor/status", "123", qos=0, retain=False) + + +async def test_debug_info_qos_retain(hass, mqtt_mock): + """Test debug info.""" + config = { + "device": {"identifiers": ["helloworld"]}, + "platform": "mqtt", + "name": "test", + "state_topic": "sensor/#", + "unique_id": "veryunique", + } + + entry = MockConfigEntry(domain=mqtt.DOMAIN) + entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, entry) + registry = await hass.helpers.device_registry.async_get_registry() + + data = json.dumps(config) + async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}, set()) + assert device is not None + + debug_info_data = await debug_info.info_for_device(hass, device.id) + assert len(debug_info_data["entities"][0]["subscriptions"]) >= 1 + assert {"topic": "sensor/#", "messages": []} in debug_info_data["entities"][0][ + "subscriptions" + ] + + start_dt = datetime(2019, 1, 1, 0, 0, 0) + with patch("homeassistant.util.dt.utcnow") as dt_utcnow: + dt_utcnow.return_value = start_dt + async_fire_mqtt_message(hass, "sensor/abc", "123", qos=0, retain=False) + async_fire_mqtt_message(hass, "sensor/abc", "123", qos=1, retain=True) + async_fire_mqtt_message(hass, "sensor/abc", "123", qos=2, retain=False) + + debug_info_data = await debug_info.info_for_device(hass, device.id) + assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 + assert { + "payload": "123", + "qos": 0, + "retain": False, + "time": start_dt, + "topic": "sensor/abc", + } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] + assert { + "payload": "123", + "qos": 1, + "retain": True, + "time": start_dt, + "topic": "sensor/abc", + } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] + assert { + "payload": "123", + "qos": 2, + "retain": False, + "time": start_dt, + "topic": "sensor/abc", + } in debug_info_data["entities"][0]["subscriptions"][0]["messages"] From e6be297fba674391db4b859b77f29e8a0bbb740d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 May 2020 11:03:20 -0500 Subject: [PATCH 196/511] Bump HAP-python to 2.8.3 (#35023) * Fixes camera support --- 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 27f83d996ad..796bb3933f7 100644 --- a/homeassistant/components/homekit/manifest.json +++ b/homeassistant/components/homekit/manifest.json @@ -2,7 +2,7 @@ "domain": "homekit", "name": "HomeKit", "documentation": "https://www.home-assistant.io/integrations/homekit", - "requirements": ["HAP-python==2.8.2","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1"], + "requirements": ["HAP-python==2.8.3","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1"], "dependencies": ["http"], "after_dependencies": ["logbook"], "codeowners": ["@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index 418bf0bdb9f..18c9f9ef407 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -35,7 +35,7 @@ Adafruit-SHT31==1.0.2 # Adafruit_BBIO==1.1.1 # homeassistant.components.homekit -HAP-python==2.8.2 +HAP-python==2.8.3 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 33bd8c3f9d0..30871402cd3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -4,7 +4,7 @@ -r requirements_test.txt # homeassistant.components.homekit -HAP-python==2.8.2 +HAP-python==2.8.3 # homeassistant.components.mobile_app # homeassistant.components.owntracks From 8661cf463a81e6e30a78ec195c329e1a5399373e Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 1 May 2020 11:29:58 -0600 Subject: [PATCH 197/511] Update AirVisual to use DataUpdateCoordinator (#34796) * Update AirVisual to use DataUpdateCoordinator * Empty commit to re-trigger build * Don't include history or trends in config flow * Code review --- .../components/airvisual/__init__.py | 171 +++++++----------- .../components/airvisual/air_quality.py | 51 +++--- .../components/airvisual/config_flow.py | 5 +- homeassistant/components/airvisual/const.py | 4 +- homeassistant/components/airvisual/sensor.py | 129 +++++++------ 5 files changed, 159 insertions(+), 201 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 4079f739824..099fdfc5df7 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -19,30 +19,23 @@ from homeassistant.const import ( ) from homeassistant.core import callback 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 -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( CONF_CITY, CONF_COUNTRY, CONF_GEOGRAPHIES, CONF_INTEGRATION_TYPE, - DATA_CLIENT, + DATA_COORDINATOR, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY, INTEGRATION_TYPE_NODE_PRO, LOGGER, - TOPIC_UPDATE, ) PLATFORMS = ["air_quality", "sensor"] -DATA_LISTENER = "listener" - DEFAULT_ATTRIBUTION = "Data provided by AirVisual" DEFAULT_GEOGRAPHY_SCAN_INTERVAL = timedelta(minutes=10) DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1) @@ -97,7 +90,7 @@ def async_get_geography_id(geography_dict): async def async_setup(hass, config): """Set up the AirVisual component.""" - hass.data[DOMAIN] = {DATA_CLIENT: {}, DATA_LISTENER: {}} + hass.data[DOMAIN] = {DATA_COORDINATOR: {}} if DOMAIN not in config: return True @@ -167,35 +160,71 @@ async def async_setup_entry(hass, config_entry): if CONF_API_KEY in config_entry.data: _standardize_geography_config_entry(hass, config_entry) - airvisual = AirVisualGeographyData( + + client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) + + async def async_update_data(): + """Get new data from the API.""" + if CONF_CITY in config_entry.data: + api_coro = client.api.city( + config_entry.data[CONF_CITY], + config_entry.data[CONF_STATE], + config_entry.data[CONF_COUNTRY], + ) + else: + api_coro = client.api.nearest_city( + config_entry.data[CONF_LATITUDE], config_entry.data[CONF_LONGITUDE], + ) + + try: + return await api_coro + except AirVisualError as err: + raise UpdateFailed(f"Error while retrieving data: {err}") + + coordinator = DataUpdateCoordinator( hass, - Client(api_key=config_entry.data[CONF_API_KEY], session=websession), - config_entry, + LOGGER, + name="geography data", + update_interval=DEFAULT_GEOGRAPHY_SCAN_INTERVAL, + update_method=async_update_data, ) # Only geography-based entries have options: config_entry.add_update_listener(async_update_options) else: _standardize_node_pro_config_entry(hass, config_entry) - airvisual = AirVisualNodeProData(hass, Client(session=websession), config_entry) - await airvisual.async_update() + client = Client(session=websession) - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = airvisual + async def async_update_data(): + """Get new data from the API.""" + try: + return await client.node.from_samba( + config_entry.data[CONF_IP_ADDRESS], + config_entry.data[CONF_PASSWORD], + include_history=False, + include_trends=False, + ) + except NodeProError as err: + raise UpdateFailed(f"Error while retrieving data: {err}") + + coordinator = DataUpdateCoordinator( + hass, + LOGGER, + name="Node/Pro data", + update_interval=DEFAULT_NODE_PRO_SCAN_INTERVAL, + update_method=async_update_data, + ) + + await coordinator.async_refresh() + + hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, component) ) - async def refresh(event_time): - """Refresh data from AirVisual.""" - await airvisual.async_update() - - hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval( - hass, refresh, airvisual.scan_interval - ) - return True @@ -248,28 +277,31 @@ async def async_unload_entry(hass, config_entry): ) ) if unload_ok: - hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) - remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) - remove_listener() + hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id) return unload_ok async def async_update_options(hass, config_entry): """Handle an options update.""" - airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - airvisual.async_update_options(config_entry.options) + coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] + await coordinator.async_request_refresh() class AirVisualEntity(Entity): """Define a generic AirVisual entity.""" - def __init__(self, airvisual): + def __init__(self, coordinator): """Initialize.""" - self._airvisual = airvisual self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._icon = None self._unit = None + self.coordinator = coordinator + + @property + def available(self): + """Return if entity is available.""" + return self.coordinator.last_update_success @property def device_state_attributes(self): @@ -295,9 +327,7 @@ class AirVisualEntity(Entity): self.update_from_latest_data() self.async_write_ha_state() - self.async_on_remove( - async_dispatcher_connect(self.hass, self._airvisual.topic_update, update) - ) + self.async_on_remove(self.coordinator.async_add_listener(update)) self.update_from_latest_data() @@ -305,76 +335,3 @@ class AirVisualEntity(Entity): def update_from_latest_data(self): """Update the entity from the latest data.""" raise NotImplementedError - - -class AirVisualGeographyData: - """Define a class to manage data from the AirVisual cloud API.""" - - def __init__(self, hass, client, config_entry): - """Initialize.""" - self._client = client - self._hass = hass - self.data = {} - self.geography_data = config_entry.data - self.geography_id = config_entry.unique_id - self.integration_type = INTEGRATION_TYPE_GEOGRAPHY - self.options = config_entry.options - self.scan_interval = DEFAULT_GEOGRAPHY_SCAN_INTERVAL - self.topic_update = TOPIC_UPDATE.format(config_entry.unique_id) - - async def async_update(self): - """Get new data for all locations from the AirVisual cloud API.""" - if CONF_CITY in self.geography_data: - api_coro = self._client.api.city( - self.geography_data[CONF_CITY], - self.geography_data[CONF_STATE], - self.geography_data[CONF_COUNTRY], - ) - else: - api_coro = self._client.api.nearest_city( - self.geography_data[CONF_LATITUDE], self.geography_data[CONF_LONGITUDE], - ) - - try: - self.data[self.geography_id] = await api_coro - except AirVisualError as err: - LOGGER.error("Error while retrieving data: %s", err) - self.data[self.geography_id] = {} - - LOGGER.debug("Received new geography data") - async_dispatcher_send(self._hass, self.topic_update) - - @callback - def async_update_options(self, options): - """Update the data manager's options.""" - self.options = options - async_dispatcher_send(self._hass, self.topic_update) - - -class AirVisualNodeProData: - """Define a class to manage data from an AirVisual Node/Pro.""" - - def __init__(self, hass, client, config_entry): - """Initialize.""" - self._client = client - self._hass = hass - self._password = config_entry.data[CONF_PASSWORD] - self.data = {} - self.integration_type = INTEGRATION_TYPE_NODE_PRO - self.ip_address = config_entry.data[CONF_IP_ADDRESS] - self.scan_interval = DEFAULT_NODE_PRO_SCAN_INTERVAL - self.topic_update = TOPIC_UPDATE.format(config_entry.data[CONF_IP_ADDRESS]) - - async def async_update(self): - """Get new data from the Node/Pro.""" - try: - self.data = await self._client.node.from_samba( - self.ip_address, self._password, include_history=False - ) - except NodeProError as err: - LOGGER.error("Error while retrieving Node/Pro data: %s", err) - self.data = {} - return - - LOGGER.debug("Received new Node/Pro data") - async_dispatcher_send(self._hass, self.topic_update) diff --git a/homeassistant/components/airvisual/air_quality.py b/homeassistant/components/airvisual/air_quality.py index 71f9f9d9fbe..bd1c10a9d84 100644 --- a/homeassistant/components/airvisual/air_quality.py +++ b/homeassistant/components/airvisual/air_quality.py @@ -4,7 +4,12 @@ from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER from homeassistant.core import callback from . import AirVisualEntity -from .const import DATA_CLIENT, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY +from .const import ( + CONF_INTEGRATION_TYPE, + DATA_COORDINATOR, + DOMAIN, + INTEGRATION_TYPE_GEOGRAPHY, +) ATTR_HUMIDITY = "humidity" ATTR_SENSOR_LIFE = "{0}_sensor_life" @@ -13,13 +18,13 @@ ATTR_VOC = "voc" async def async_setup_entry(hass, config_entry, async_add_entities): """Set up AirVisual air quality entities based on a config entry.""" - airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] # Geography-based AirVisual integrations don't utilize this platform: - if airvisual.integration_type == INTEGRATION_TYPE_GEOGRAPHY: + if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: return - async_add_entities([AirVisualNodeProSensor(airvisual)], True) + async_add_entities([AirVisualNodeProSensor(coordinator)], True) class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity): @@ -35,69 +40,71 @@ class AirVisualNodeProSensor(AirVisualEntity, AirQualityEntity): @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" - if self._airvisual.data["current"]["settings"]["is_aqi_usa"]: - return self._airvisual.data["current"]["measurements"]["aqi_us"] - return self._airvisual.data["current"]["measurements"]["aqi_cn"] + if self.coordinator.data["current"]["settings"]["is_aqi_usa"]: + return self.coordinator.data["current"]["measurements"]["aqi_us"] + return self.coordinator.data["current"]["measurements"]["aqi_cn"] @property def available(self): """Return True if entity is available.""" - return bool(self._airvisual.data) + return bool(self.coordinator.data) @property def carbon_dioxide(self): """Return the CO2 (carbon dioxide) level.""" - return self._airvisual.data["current"]["measurements"].get("co2_ppm") + return self.coordinator.data["current"]["measurements"].get("co2") @property def device_info(self): """Return device registry information for this entity.""" return { - "identifiers": {(DOMAIN, self._airvisual.data["current"]["serial_number"])}, - "name": self._airvisual.data["current"]["settings"]["node_name"], + "identifiers": { + (DOMAIN, self.coordinator.data["current"]["serial_number"]) + }, + "name": self.coordinator.data["current"]["settings"]["node_name"], "manufacturer": "AirVisual", - "model": f'{self._airvisual.data["current"]["status"]["model"]}', + "model": f'{self.coordinator.data["current"]["status"]["model"]}', "sw_version": ( - f'Version {self._airvisual.data["current"]["status"]["system_version"]}' - f'{self._airvisual.data["current"]["status"]["app_version"]}' + f'Version {self.coordinator.data["current"]["status"]["system_version"]}' + f'{self.coordinator.data["current"]["status"]["app_version"]}' ), } @property def name(self): """Return the name.""" - node_name = self._airvisual.data["current"]["settings"]["node_name"] + node_name = self.coordinator.data["current"]["settings"]["node_name"] return f"{node_name} Node/Pro: Air Quality" @property def particulate_matter_2_5(self): """Return the particulate matter 2.5 level.""" - return self._airvisual.data["current"]["measurements"].get("pm2_5") + return self.coordinator.data["current"]["measurements"].get("pm2_5") @property def particulate_matter_10(self): """Return the particulate matter 10 level.""" - return self._airvisual.data["current"]["measurements"].get("pm1_0") + return self.coordinator.data["current"]["measurements"].get("pm1_0") @property def particulate_matter_0_1(self): """Return the particulate matter 0.1 level.""" - return self._airvisual.data["current"]["measurements"].get("pm0_1") + return self.coordinator.data["current"]["measurements"].get("pm0_1") @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return self._airvisual.data["current"]["serial_number"] + return self.coordinator.data["current"]["serial_number"] @callback def update_from_latest_data(self): - """Update from the Node/Pro's data.""" + """Update the entity from the latest data.""" self._attrs.update( { - ATTR_VOC: self._airvisual.data["current"]["measurements"].get("voc"), + ATTR_VOC: self.coordinator.data["current"]["measurements"].get("voc"), **{ ATTR_SENSOR_LIFE.format(pollutant): lifespan - for pollutant, lifespan in self._airvisual.data["current"][ + for pollutant, lifespan in self.coordinator.data["current"][ "status" ]["sensor_life"].items() }, diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index 691fa19504a..abbc2df9061 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -146,7 +146,10 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): try: await client.node.from_samba( - user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD] + user_input[CONF_IP_ADDRESS], + user_input[CONF_PASSWORD], + include_history=False, + include_trends=False, ) except NodeProError as err: LOGGER.error("Error connecting to Node/Pro unit: %s", err) diff --git a/homeassistant/components/airvisual/const.py b/homeassistant/components/airvisual/const.py index 0e0e62a9b0c..a98a899b762 100644 --- a/homeassistant/components/airvisual/const.py +++ b/homeassistant/components/airvisual/const.py @@ -12,6 +12,4 @@ CONF_COUNTRY = "country" CONF_GEOGRAPHIES = "geographies" CONF_INTEGRATION_TYPE = "integration_type" -DATA_CLIENT = "client" - -TOPIC_UPDATE = f"airvisual_update_{0}" +DATA_COORDINATOR = "coordinator" diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 5009788e6fa..b122f3c27b4 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -24,7 +24,8 @@ from . import AirVisualEntity from .const import ( CONF_CITY, CONF_COUNTRY, - DATA_CLIENT, + CONF_INTEGRATION_TYPE, + DATA_COORDINATOR, DOMAIN, INTEGRATION_TYPE_GEOGRAPHY, ) @@ -92,68 +93,53 @@ POLLUTANT_MAPPING = { async def async_setup_entry(hass, config_entry, async_add_entities): """Set up AirVisual sensors based on a config entry.""" - airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] + coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] - if airvisual.integration_type == INTEGRATION_TYPE_GEOGRAPHY: + if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: sensors = [ AirVisualGeographySensor( - airvisual, kind, name, icon, unit, locale, geography_id, + coordinator, config_entry, kind, name, icon, unit, locale, ) - for geography_id in airvisual.data for locale in GEOGRAPHY_SENSOR_LOCALES for kind, name, icon, unit in GEOGRAPHY_SENSORS ] else: sensors = [ - AirVisualNodeProSensor(airvisual, kind, name, device_class, unit) + AirVisualNodeProSensor(coordinator, kind, name, device_class, unit) for kind, name, device_class, unit in NODE_PRO_SENSORS ] async_add_entities(sensors, True) -class AirVisualSensor(AirVisualEntity): - """Define a generic AirVisual sensor.""" +class AirVisualGeographySensor(AirVisualEntity): + """Define an AirVisual sensor related to geography data via the Cloud API.""" - def __init__(self, airvisual, kind, name, unit): + def __init__(self, coordinator, config_entry, kind, name, icon, unit, locale): """Initialize.""" - super().__init__(airvisual) + super().__init__(coordinator) + self._attrs.update( + { + ATTR_CITY: config_entry.data.get(CONF_CITY), + ATTR_STATE: config_entry.data.get(CONF_STATE), + ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY), + } + ) + self._config_entry = config_entry + self._icon = icon self._kind = kind + self._locale = locale self._name = name self._state = None self._unit = unit - @property - def state(self): - """Return the state.""" - return self._state - - -class AirVisualGeographySensor(AirVisualSensor): - """Define an AirVisual sensor related to geography data via the Cloud API.""" - - def __init__(self, airvisual, kind, name, icon, unit, locale, geography_id): - """Initialize.""" - super().__init__(airvisual, kind, name, unit) - - self._attrs.update( - { - ATTR_CITY: airvisual.data[geography_id].get(CONF_CITY), - ATTR_STATE: airvisual.data[geography_id].get(CONF_STATE), - ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY), - } - ) - self._geography_id = geography_id - self._icon = icon - self._locale = locale - @property def available(self): """Return True if entity is available.""" try: - return bool( - self._airvisual.data[self._geography_id]["current"]["pollution"] + return self.coordinator.last_update_success and bool( + self.coordinator.data["current"]["pollution"] ) except KeyError: return False @@ -163,16 +149,21 @@ class AirVisualGeographySensor(AirVisualSensor): """Return the name.""" return f"{GEOGRAPHY_SENSOR_LOCALES[self._locale]} {self._name}" + @property + def state(self): + """Return the state.""" + return self._state + @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return f"{self._geography_id}_{self._locale}_{self._kind}" + return f"{self._config_entry.unique_id}_{self._locale}_{self._kind}" @callback def update_from_latest_data(self): - """Update the sensor.""" + """Update the entity from the latest data.""" try: - data = self._airvisual.data[self._geography_id]["current"]["pollution"] + data = self.coordinator.data["current"]["pollution"] except KeyError: return @@ -197,36 +188,31 @@ class AirVisualGeographySensor(AirVisualSensor): } ) - if CONF_LATITUDE in self._airvisual.geography_data: - if self._airvisual.options[CONF_SHOW_ON_MAP]: - self._attrs[ATTR_LATITUDE] = self._airvisual.geography_data[ - CONF_LATITUDE - ] - self._attrs[ATTR_LONGITUDE] = self._airvisual.geography_data[ - CONF_LONGITUDE - ] + if CONF_LATITUDE in self._config_entry.data: + if self._config_entry.options[CONF_SHOW_ON_MAP]: + self._attrs[ATTR_LATITUDE] = self._config_entry.data[CONF_LATITUDE] + self._attrs[ATTR_LONGITUDE] = self._config_entry.data[CONF_LONGITUDE] self._attrs.pop("lati", None) self._attrs.pop("long", None) else: - self._attrs["lati"] = self._airvisual.geography_data[CONF_LATITUDE] - self._attrs["long"] = self._airvisual.geography_data[CONF_LONGITUDE] + self._attrs["lati"] = self._config_entry.data[CONF_LATITUDE] + self._attrs["long"] = self._config_entry.data[CONF_LONGITUDE] self._attrs.pop(ATTR_LATITUDE, None) self._attrs.pop(ATTR_LONGITUDE, None) -class AirVisualNodeProSensor(AirVisualSensor): +class AirVisualNodeProSensor(AirVisualEntity): """Define an AirVisual sensor related to a Node/Pro unit.""" - def __init__(self, airvisual, kind, name, device_class, unit): + def __init__(self, coordinator, kind, name, device_class, unit): """Initialize.""" - super().__init__(airvisual, kind, name, unit) + super().__init__(coordinator) self._device_class = device_class - - @property - def available(self): - """Return True if entity is available.""" - return bool(self._airvisual.data) + self._kind = kind + self._name = name + self._state = None + self._unit = unit @property def device_class(self): @@ -237,37 +223,44 @@ class AirVisualNodeProSensor(AirVisualSensor): def device_info(self): """Return device registry information for this entity.""" return { - "identifiers": {(DOMAIN, self._airvisual.data["current"]["serial_number"])}, - "name": self._airvisual.data["current"]["settings"]["node_name"], + "identifiers": { + (DOMAIN, self.coordinator.data["current"]["serial_number"]) + }, + "name": self.coordinator.data["current"]["settings"]["node_name"], "manufacturer": "AirVisual", - "model": f'{self._airvisual.data["current"]["status"]["model"]}', + "model": f'{self.coordinator.data["current"]["status"]["model"]}', "sw_version": ( - f'Version {self._airvisual.data["current"]["status"]["system_version"]}' - f'{self._airvisual.data["current"]["status"]["app_version"]}' + f'Version {self.coordinator.data["current"]["status"]["system_version"]}' + f'{self.coordinator.data["current"]["status"]["app_version"]}' ), } @property def name(self): """Return the name.""" - node_name = self._airvisual.data["current"]["settings"]["node_name"] + node_name = self.coordinator.data["current"]["settings"]["node_name"] return f"{node_name} Node/Pro: {self._name}" + @property + def state(self): + """Return the state.""" + return self._state + @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return f"{self._airvisual.data['current']['serial_number']}_{self._kind}" + return f"{self.coordinator.data['current']['serial_number']}_{self._kind}" @callback def update_from_latest_data(self): - """Update from the Node/Pro's data.""" + """Update the entity from the latest data.""" if self._kind == SENSOR_KIND_BATTERY_LEVEL: - self._state = self._airvisual.data["current"]["status"]["battery"] + self._state = self.coordinator.data["current"]["status"]["battery"] elif self._kind == SENSOR_KIND_HUMIDITY: - self._state = self._airvisual.data["current"]["measurements"].get( + self._state = self.coordinator.data["current"]["measurements"].get( "humidity" ) elif self._kind == SENSOR_KIND_TEMPERATURE: - self._state = self._airvisual.data["current"]["measurements"].get( + self._state = self.coordinator.data["current"]["measurements"].get( "temperature_C" ) From a65e656c038178a4fa795189981a7046e9b61600 Mon Sep 17 00:00:00 2001 From: Mich-b Date: Fri, 1 May 2020 19:31:39 +0200 Subject: [PATCH 198/511] Add more SNMP variable types (#33426) * added support for more SNMP variable types * Fix SNMP pull request formatting * retry fix linting errors * Created SNMP vartype dict * Moved to Integer instead of Integer32 as default vartype * Update homeassistant/components/snmp/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/snmp/switch.py Co-authored-by: Martin Hjelmare * Update homeassistant/components/snmp/switch.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/snmp/const.py | 2 + homeassistant/components/snmp/switch.py | 60 +++++++++++++++++++++---- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/snmp/const.py b/homeassistant/components/snmp/const.py index 90e445da554..b3a93cfe98b 100644 --- a/homeassistant/components/snmp/const.py +++ b/homeassistant/components/snmp/const.py @@ -8,6 +8,7 @@ CONF_DEFAULT_VALUE = "default_value" CONF_PRIV_KEY = "priv_key" CONF_PRIV_PROTOCOL = "priv_protocol" CONF_VERSION = "version" +CONF_VARTYPE = "vartype" DEFAULT_AUTH_PROTOCOL = "none" DEFAULT_COMMUNITY = "public" @@ -16,6 +17,7 @@ DEFAULT_NAME = "SNMP" DEFAULT_PORT = "161" DEFAULT_PRIV_PROTOCOL = "none" DEFAULT_VERSION = "1" +DEFAULT_VARTYPE = "none" SNMP_VERSIONS = {"1": 0, "2c": 1, "3": None} diff --git a/homeassistant/components/snmp/switch.py b/homeassistant/components/snmp/switch.py index 18effc563bb..7210c8e5fd3 100644 --- a/homeassistant/components/snmp/switch.py +++ b/homeassistant/components/snmp/switch.py @@ -1,7 +1,6 @@ """Support for SNMP enabled switch.""" import logging -from pyasn1.type.univ import Integer import pysnmp.hlapi.asyncio as hlapi from pysnmp.hlapi.asyncio import ( CommunityData, @@ -14,6 +13,20 @@ from pysnmp.hlapi.asyncio import ( getCmd, setCmd, ) +from pysnmp.proto.rfc1902 import ( + Counter32, + Counter64, + Gauge32, + Integer, + Integer32, + IpAddress, + Null, + ObjectIdentifier, + OctetString, + Opaque, + TimeTicks, + Unsigned32, +) import voluptuous as vol from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity @@ -34,12 +47,14 @@ from .const import ( CONF_COMMUNITY, CONF_PRIV_KEY, CONF_PRIV_PROTOCOL, + CONF_VARTYPE, CONF_VERSION, DEFAULT_AUTH_PROTOCOL, DEFAULT_HOST, DEFAULT_NAME, DEFAULT_PORT, DEFAULT_PRIV_PROTOCOL, + DEFAULT_VARTYPE, DEFAULT_VERSION, MAP_AUTH_PROTOCOLS, MAP_PRIV_PROTOCOLS, @@ -56,6 +71,22 @@ DEFAULT_COMMUNITY = "private" DEFAULT_PAYLOAD_OFF = 0 DEFAULT_PAYLOAD_ON = 1 +MAP_SNMP_VARTYPES = { + "Counter32": Counter32, + "Counter64": Counter64, + "Gauge32": Gauge32, + "Integer32": Integer32, + "Integer": Integer, + "IpAddress": IpAddress, + "Null": Null, + # some work todo to support tuple ObjectIdentifier, this just supports str + "ObjectIdentifier": ObjectIdentifier, + "OctetString": OctetString, + "Opaque": Opaque, + "TimeTicks": TimeTicks, + "Unsigned32": Unsigned32, +} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_BASEOID): cv.string, @@ -78,6 +109,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( vol.Optional(CONF_PRIV_PROTOCOL, default=DEFAULT_PRIV_PROTOCOL): vol.In( MAP_PRIV_PROTOCOLS ), + vol.Optional(CONF_VARTYPE, default=DEFAULT_VARTYPE): cv.string, } ) @@ -100,6 +132,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= privproto = config.get(CONF_PRIV_PROTOCOL) payload_on = config.get(CONF_PAYLOAD_ON) payload_off = config.get(CONF_PAYLOAD_OFF) + vartype = config.get(CONF_VARTYPE) async_add_entities( [ @@ -120,6 +153,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= payload_off, command_payload_on, command_payload_off, + vartype, ) ], True, @@ -147,11 +181,13 @@ class SnmpSwitch(SwitchEntity): payload_off, command_payload_on, command_payload_off, + vartype, ): """Initialize the switch.""" self._name = name self._baseoid = baseoid + self._vartype = vartype # Set the command OID to the base OID if command OID is unset self._commandoid = commandoid or baseoid @@ -191,17 +227,24 @@ class SnmpSwitch(SwitchEntity): async def async_turn_on(self, **kwargs): """Turn on the switch.""" - if self._command_payload_on.isdigit(): - await self._set(Integer(self._command_payload_on)) - else: - await self._set(self._command_payload_on) + # If vartype set, use it - http://snmplabs.com/pysnmp/docs/api-reference.html#pysnmp.smi.rfc1902.ObjectType + await self._execute_command(self._command_payload_on) async def async_turn_off(self, **kwargs): """Turn off the switch.""" - if self._command_payload_on.isdigit(): - await self._set(Integer(self._command_payload_off)) + await self._execute_command(self._command_payload_off) + + async def _execute_command(self, command): + # User did not set vartype and command is not a digit + if self._vartype == "none" and not self._command_payload_on.isdigit(): + await self._set(command) + # User set vartype Null, command must be an empty string + elif self._vartype == "Null": + await self._set(Null)("") + # user did not set vartype but command is digit: defaulting to Integer + # or user did set vartype else: - await self._set(self._command_payload_off) + await self._set(MAP_SNMP_VARTYPES.get(self._vartype, Integer)(command)) async def async_update(self): """Update the state.""" @@ -241,7 +284,6 @@ class SnmpSwitch(SwitchEntity): return self._state async def _set(self, value): - await setCmd( *self._request_args, ObjectType(ObjectIdentity(self._commandoid), value) ) From 9436645648212749765465cd0054bb3d5d42b43c Mon Sep 17 00:00:00 2001 From: Teemu R Date: Fri, 1 May 2020 19:43:30 +0200 Subject: [PATCH 199/511] Fix songpal on devices where source!=uri (#34699) --- homeassistant/components/songpal/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index e2a9d9c5d57..55d8f0133a9 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -164,7 +164,7 @@ class SongpalDevice(MediaPlayerEntity): async def _source_changed(content: ContentChange): _LOGGER.debug("Source changed: %s", content) if content.is_input: - self._active_source = self._sources[content.source] + self._active_source = self._sources[content.uri] _LOGGER.debug("New active source: %s", self._active_source) self.async_write_ha_state() else: From b3201523aa66614323ac703c6ed75109f5405e91 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 May 2020 11:34:09 -0700 Subject: [PATCH 200/511] Fix translation merging for custom components without translations (#35032) --- homeassistant/helpers/translation.py | 9 ++++++--- tests/helpers/test_translation.py | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/helpers/translation.py b/homeassistant/helpers/translation.py index abf39972186..d0fac953ac1 100644 --- a/homeassistant/helpers/translation.py +++ b/homeassistant/helpers/translation.py @@ -210,6 +210,9 @@ async def async_get_component_strings( else: files_to_load[loaded] = path + if not files_to_load: + return translations + # Load files load_translations_job = hass.async_add_executor_job( load_translations_files, files_to_load @@ -218,12 +221,12 @@ async def async_get_component_strings( loaded_translations = await load_translations_job # Translations that miss "title" will get integration put in. - for loaded, translations in loaded_translations.items(): + for loaded, loaded_translation in loaded_translations.items(): if "." in loaded: continue - if "title" not in translations: - translations["title"] = integrations[loaded].name + if "title" not in loaded_translation: + loaded_translation["title"] = integrations[loaded].name translations.update(loaded_translations) diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 8596b9dd7f3..77e55a1d6ed 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -267,3 +267,11 @@ async def test_caching(hass): await translation.async_get_translations(hass, "en", "state") assert len(mock_merge.mock_calls) == 2 + + +async def test_custom_component_translations(hass): + """Test getting translation from custom components.""" + hass.config.components.add("test_standalone") + hass.config.components.add("test_embedded") + hass.config.components.add("test_package") + assert await translation.async_get_translations(hass, "en", "state") == {} From e1ae455f1d8507d4bdd007d1a415fc44609fdda7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 1 May 2020 20:35:30 +0200 Subject: [PATCH 201/511] Fix ONVIF YAML import (#35035) --- homeassistant/components/onvif/__init__.py | 30 ++++++++++++------- homeassistant/components/onvif/config_flow.py | 21 ++++++------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 5fe43fdd83b..eed5a20e3cc 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -5,15 +5,23 @@ import voluptuous as vol from homeassistant.components.ffmpeg import CONF_EXTRA_ARGUMENTS from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_per_platform from .const import ( - CONF_PROFILE, CONF_RTSP_TRANSPORT, DEFAULT_ARGUMENTS, - DEFAULT_PROFILE, + DEFAULT_NAME, + DEFAULT_PASSWORD, + DEFAULT_PORT, + DEFAULT_USERNAME, DOMAIN, RTSP_TRANS_PROTOCOLS, ) @@ -32,12 +40,14 @@ async def async_setup(hass: HomeAssistant, config: dict): continue config = p_config.copy() - profile = config.get(CONF_PROFILE, DEFAULT_PROFILE) if config[CONF_HOST] not in configs.keys(): - configs[config[CONF_HOST]] = config - configs[config[CONF_HOST]][CONF_PROFILE] = [profile] - else: - configs[config[CONF_HOST]][CONF_PROFILE].append(profile) + configs[config[CONF_HOST]] = { + CONF_HOST: config[CONF_HOST], + CONF_NAME: config.get(CONF_NAME, DEFAULT_NAME), + CONF_PASSWORD: config.get(CONF_PASSWORD, DEFAULT_PASSWORD), + CONF_PORT: config.get(CONF_PORT, DEFAULT_PORT), + CONF_USERNAME: config.get(CONF_USERNAME, DEFAULT_USERNAME), + } for conf in configs.values(): hass.async_create_task( @@ -64,7 +74,7 @@ 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( + return all( await asyncio.gather( *[ hass.config_entries.async_forward_entry_unload(entry, component) @@ -73,8 +83,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) - return unload_ok - async def async_populate_options(hass, entry): """Populate default options for device.""" diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index d5b153e2747..c3fe3b6d4b7 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -125,21 +125,20 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): discovery = await async_discovery(self.hass) for device in discovery: - configured = False - for entry in self._async_current_entries(): - if entry.unique_id == device[CONF_DEVICE_ID]: - configured = True - break + configured = any( + entry.unique_id == device[CONF_DEVICE_ID] + for entry in self._async_current_entries() + ) + if not configured: self.devices.append(device) LOGGER.debug("Discovered ONVIF devices %s", pformat(self.devices)) if self.devices: - names = [] - - for device in self.devices: - names.append(f"{device[CONF_NAME]} ({device[CONF_HOST]})") + names = [ + f"{device[CONF_NAME]} ({device[CONF_HOST]})" for device in self.devices + ] names.append(CONF_MANUAL_INPUT) @@ -299,7 +298,7 @@ def get_device(hass, host, port, username, password) -> ONVIFCamera: """Get ONVIFCamera instance.""" session = async_get_clientsession(hass) transport = AsyncTransport(None, session=session) - device = ONVIFCamera( + return ONVIFCamera( host, port, username, @@ -307,5 +306,3 @@ def get_device(hass, host, port, username, password) -> ONVIFCamera: f"{os.path.dirname(onvif.__file__)}/wsdl/", transport=transport, ) - - return device From ecdcfb835dc708aa8cd035adbe41dfb104203586 Mon Sep 17 00:00:00 2001 From: Teemu R Date: Fri, 1 May 2020 21:00:44 +0200 Subject: [PATCH 202/511] Add yeelight meteorite (YLDL01YL, ceiling10) (#35018) --- homeassistant/components/yeelight/light.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 49315117e72..29f943906d6 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -141,6 +141,7 @@ MODEL_TO_DEVICE_TYPE = { "ceiling2": BulbType.WhiteTemp, "ceiling3": BulbType.WhiteTemp, "ceiling4": BulbType.WhiteTempMood, + "ceiling10": BulbType.WhiteTempMood, "ceiling13": BulbType.WhiteTemp, } From dff2ee21562bd63f6938c3bd2adb4c44eb8b8167 Mon Sep 17 00:00:00 2001 From: Quentame Date: Sat, 2 May 2020 01:31:39 +0200 Subject: [PATCH 203/511] Bump python-synology to 0.7.4 (#35052) --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index bb3c5ce3aec..4a538606ecb 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.3"], + "requirements": ["python-synology==0.7.4"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index 18c9f9ef407..ee8d53035c7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1692,7 +1692,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.3 +python-synology==0.7.4 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 30871402cd3..fb7716dd38c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -662,7 +662,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.3 +python-synology==0.7.4 # homeassistant.components.tado python-tado==0.8.1 From f4f2aff5b69c5be44f6148964ccded2e600cd104 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 2 May 2020 00:04:57 +0000 Subject: [PATCH 204/511] [ci skip] Translation update --- .../components/airly/translations/ca.json | 2 +- .../airvisual/translations/es-419.json | 19 +++- .../almond/translations/es-419.json | 12 +- .../ambiclimate/translations/ca.json | 2 +- .../ambient_station/translations/es-419.json | 3 + .../components/atag/translations/es-419.json | 19 ++++ .../august/translations/es-419.json | 7 +- .../binary_sensor/translations/es-419.json | 4 +- .../components/braviatv/translations/ca.json | 2 +- .../braviatv/translations/es-419.json | 31 ++++++ .../brother/translations/es-419.json | 17 +++ .../cert_expiry/translations/es-419.json | 3 + .../deconz/translations/es-419.json | 5 + .../components/doorbird/translations/ca.json | 2 +- .../doorbird/translations/es-419.json | 30 +++++ .../ecobee/translations/es-419.json | 5 + .../components/esphome/translations/ca.json | 3 +- .../components/esphome/translations/cs.json | 21 +++- .../components/esphome/translations/fr.json | 3 +- .../components/esphome/translations/pl.json | 3 +- .../components/esphome/translations/ru.json | 3 +- .../esphome/translations/zh-Hant.json | 3 +- .../components/gios/translations/ca.json | 2 +- .../components/glances/translations/ca.json | 2 +- .../components/harmony/translations/ca.json | 4 +- .../components/homekit/translations/ca.json | 53 +++++++++ .../components/homekit/translations/cs.json | 3 + .../components/homekit/translations/en.json | 105 +++++++++--------- .../components/homekit/translations/fr.json | 53 +++++++++ .../components/homekit/translations/pl.json | 35 ++++++ .../components/homekit/translations/ru.json | 53 +++++++++ .../homekit/translations/zh-Hant.json | 53 +++++++++ .../homekit_controller/translations/ca.json | 2 +- .../huawei_lte/translations/ca.json | 2 +- .../translations/fr.json | 23 ++++ .../translations/pl.json | 24 ++++ .../components/icloud/translations/ca.json | 2 +- .../components/konnected/translations/ca.json | 2 +- .../components/life360/translations/ca.json | 2 +- .../components/linky/translations/ca.json | 2 +- .../logi_circle/translations/ca.json | 6 +- .../components/melcloud/translations/ca.json | 2 +- .../minecraft_server/translations/ca.json | 2 +- .../mobile_app/translations/ca.json | 2 +- .../components/nuheat/translations/ca.json | 2 +- .../components/onvif/translations/ca.json | 58 ++++++++++ .../components/onvif/translations/cs.json | 56 ++++++++++ .../components/onvif/translations/en.json | 3 +- .../components/onvif/translations/fr.json | 57 ++++++++++ .../components/onvif/translations/no.json | 12 ++ .../components/onvif/translations/pl.json | 58 ++++++++++ .../components/onvif/translations/ru.json | 58 ++++++++++ .../onvif/translations/zh-Hant.json | 58 ++++++++++ .../opentherm_gw/translations/ca.json | 2 +- .../components/plex/translations/ca.json | 2 +- .../components/powerwall/translations/ca.json | 2 +- .../pvpc_hourly_pricing/translations/ca.json | 2 +- .../components/rachio/translations/ca.json | 2 +- .../components/samsungtv/translations/hu.json | 6 +- .../smartthings/translations/ca.json | 2 +- .../components/solaredge/translations/ca.json | 4 +- .../components/starline/translations/ca.json | 2 +- .../synology_dsm/translations/ca.json | 2 +- .../totalconnect/translations/ca.json | 2 +- .../transmission/translations/ca.json | 2 +- .../transmission/translations/es-419.json | 11 ++ .../twentemilieu/translations/es-419.json | 4 + .../components/unifi/translations/ca.json | 12 +- .../components/unifi/translations/cs.json | 7 ++ .../components/unifi/translations/en.json | 6 +- .../components/unifi/translations/fr.json | 7 ++ .../components/unifi/translations/pl.json | 3 + .../components/unifi/translations/ru.json | 8 ++ .../unifi/translations/zh-Hant.json | 8 ++ .../components/vesync/translations/ca.json | 2 +- .../components/vilfo/translations/es-419.json | 9 ++ .../components/vizio/translations/ca.json | 2 +- .../components/vizio/translations/es-419.json | 24 ++++ .../components/withings/translations/ca.json | 2 +- .../xiaomi_miio/translations/cs.json | 29 +++++ 80 files changed, 1050 insertions(+), 109 deletions(-) create mode 100644 homeassistant/components/atag/translations/es-419.json create mode 100644 homeassistant/components/braviatv/translations/es-419.json create mode 100644 homeassistant/components/doorbird/translations/es-419.json create mode 100644 homeassistant/components/homekit/translations/ca.json create mode 100644 homeassistant/components/homekit/translations/cs.json create mode 100644 homeassistant/components/homekit/translations/fr.json create mode 100644 homeassistant/components/homekit/translations/pl.json create mode 100644 homeassistant/components/homekit/translations/ru.json create mode 100644 homeassistant/components/homekit/translations/zh-Hant.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/fr.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/pl.json create mode 100644 homeassistant/components/onvif/translations/ca.json create mode 100644 homeassistant/components/onvif/translations/cs.json create mode 100644 homeassistant/components/onvif/translations/fr.json create mode 100644 homeassistant/components/onvif/translations/no.json create mode 100644 homeassistant/components/onvif/translations/pl.json create mode 100644 homeassistant/components/onvif/translations/ru.json create mode 100644 homeassistant/components/onvif/translations/zh-Hant.json create mode 100644 homeassistant/components/transmission/translations/es-419.json create mode 100644 homeassistant/components/vilfo/translations/es-419.json create mode 100644 homeassistant/components/vizio/translations/es-419.json create mode 100644 homeassistant/components/xiaomi_miio/translations/cs.json diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 76e8702e8fc..3caf870ccdf 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -15,7 +15,7 @@ "longitude": "Longitud", "name": "Nom de la integraci\u00f3" }, - "description": "Configura una integraci\u00f3 de qualitat d\u2019aire Airly. Per generar la clau API, v\u00e9s a https://developer.airly.eu/register", + "description": "Configura una integraci\u00f3 de qualitat d'aire Airly. Per generar la clau API, v\u00e9s a https://developer.airly.eu/register", "title": "Airly" } } diff --git a/homeassistant/components/airvisual/translations/es-419.json b/homeassistant/components/airvisual/translations/es-419.json index ada76676ca9..aea8c1ad574 100644 --- a/homeassistant/components/airvisual/translations/es-419.json +++ b/homeassistant/components/airvisual/translations/es-419.json @@ -7,12 +7,29 @@ "invalid_api_key": "Clave de API inv\u00e1lida" }, "step": { - "user": { + "geography": { "data": { "api_key": "Clave API", "latitude": "Latitud", "longitude": "Longitud" }, + "title": "Configurar una geograf\u00eda" + }, + "node_pro": { + "data": { + "password": "Contrase\u00f1a de la unidad" + }, + "title": "Configurar un AirVisual Node/Pro" + }, + "user": { + "data": { + "api_key": "Clave API", + "cloud_api": "Localizaci\u00f3n geogr\u00e1fica", + "latitude": "Latitud", + "longitude": "Longitud", + "node_pro": "AirVisual Node Pro", + "type": "Tipo de integraci\u00f3n" + }, "description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.", "title": "Configurar AirVisual" } diff --git a/homeassistant/components/almond/translations/es-419.json b/homeassistant/components/almond/translations/es-419.json index 7b7f7aea9ca..fbcf901c2e5 100644 --- a/homeassistant/components/almond/translations/es-419.json +++ b/homeassistant/components/almond/translations/es-419.json @@ -2,7 +2,17 @@ "config": { "abort": { "already_setup": "Solo puede configurar una cuenta Almond.", - "cannot_connect": "No se puede conectar con el servidor Almond." + "cannot_connect": "No se puede conectar con el servidor Almond.", + "missing_configuration": "Por favor, consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond." + }, + "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" + }, + "pick_implementation": { + "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" + } } } } \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/ca.json b/homeassistant/components/ambiclimate/translations/ca.json index 14ebf481a36..0b8ca963813 100644 --- a/homeassistant/components/ambiclimate/translations/ca.json +++ b/homeassistant/components/ambiclimate/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "access_token": "S'ha produ\u00eft un error desconegut al generat un token d'acc\u00e9s.", - "already_setup": "El compte d\u2019Ambi Climate est\u00e0 configurat.", + "already_setup": "El compte d'Ambi Climate est\u00e0 configurat.", "no_config": "Necessites configurar Ambiclimate abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/ambiclimate/)." }, "create_entry": { diff --git a/homeassistant/components/ambient_station/translations/es-419.json b/homeassistant/components/ambient_station/translations/es-419.json index d2c60aee5a0..b16c5af9c62 100644 --- a/homeassistant/components/ambient_station/translations/es-419.json +++ b/homeassistant/components/ambient_station/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Esta clave de aplicaci\u00f3n ya est\u00e1 en uso." + }, "error": { "invalid_key": "Clave de API y/o clave de aplicaci\u00f3n no v\u00e1lida", "no_devices": "No se han encontrado dispositivos en la cuenta." diff --git a/homeassistant/components/atag/translations/es-419.json b/homeassistant/components/atag/translations/es-419.json new file mode 100644 index 00000000000..a833218e311 --- /dev/null +++ b/homeassistant/components/atag/translations/es-419.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Solo se puede agregar un dispositivo Atag a Home Assistant" + }, + "error": { + "connection_error": "No se pudo conectar, intente nuevamente" + }, + "step": { + "user": { + "data": { + "port": "Puerto (10000)" + }, + "title": "Conectarse al dispositivo" + } + } + }, + "title": "Atag" +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/es-419.json b/homeassistant/components/august/translations/es-419.json index 0732c1c5e48..914aea1b801 100644 --- a/homeassistant/components/august/translations/es-419.json +++ b/homeassistant/components/august/translations/es-419.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "La cuenta ya est\u00e1 configurada" + }, "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", "unknown": "Error inesperado" }, @@ -12,7 +16,8 @@ "timeout": "Tiempo de espera (segundos)", "username": "Nombre de usuario" }, - "description": "Si el M\u00e9todo de inicio de sesi\u00f3n es 'correo electr\u00f3nico', Nombre de usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el M\u00e9todo de inicio de sesi\u00f3n es 'tel\u00e9fono', Nombre de usuario es el n\u00famero de tel\u00e9fono en el formato '+NNNNNNNNN'." + "description": "Si el M\u00e9todo de inicio de sesi\u00f3n es 'correo electr\u00f3nico', Nombre de usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el M\u00e9todo de inicio de sesi\u00f3n es 'tel\u00e9fono', Nombre de usuario es el n\u00famero de tel\u00e9fono en el formato '+NNNNNNNNN'.", + "title": "Configurar una cuenta de August" }, "validation": { "data": { diff --git a/homeassistant/components/binary_sensor/translations/es-419.json b/homeassistant/components/binary_sensor/translations/es-419.json index 3954724934b..5bada49741e 100644 --- a/homeassistant/components/binary_sensor/translations/es-419.json +++ b/homeassistant/components/binary_sensor/translations/es-419.json @@ -80,7 +80,9 @@ "smoke": "{entity_name} comenz\u00f3 a detectar humo", "sound": "{entity_name} comenz\u00f3 a detectar sonido", "turned_off": "{entity_name} apagado", - "turned_on": "{entity_name} encendido" + "turned_on": "{entity_name} encendido", + "unsafe": "{entity_name} se volvi\u00f3 inseguro", + "vibration": "{entity_name} comenz\u00f3 a detectar vibraciones" } }, "state": { diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index d92525ae325..5a6d50c5c53 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "Nom d\u2019amfitri\u00f3 o adre\u00e7a IP del televisor" + "host": "Nom d'amfitri\u00f3 o adre\u00e7a IP del televisor" }, "description": "Configura la integraci\u00f3 de televisor Sony Bravia. Si tens problemes durant la configuraci\u00f3, v\u00e9s a: https://www.home-assistant.io/integrations/braviatv\n\nAssegura't que el televisor estigui engegat.", "title": "Televisor Sony Bravia" diff --git a/homeassistant/components/braviatv/translations/es-419.json b/homeassistant/components/braviatv/translations/es-419.json new file mode 100644 index 00000000000..48457826a52 --- /dev/null +++ b/homeassistant/components/braviatv/translations/es-419.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Esta televisi\u00f3n ya est\u00e1 configurada." + }, + "error": { + "unsupported_model": "Su modelo de televisi\u00f3n no es compatible." + }, + "step": { + "authorize": { + "data": { + "pin": "C\u00f3digo PIN" + }, + "title": "Autorizar Sony Bravia TV" + }, + "user": { + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Lista de fuentes ignoradas" + }, + "title": "Opciones para Sony Bravia TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/es-419.json b/homeassistant/components/brother/translations/es-419.json index 337f5b624fb..0cb35449bc5 100644 --- a/homeassistant/components/brother/translations/es-419.json +++ b/homeassistant/components/brother/translations/es-419.json @@ -7,6 +7,23 @@ "error": { "connection_error": "Error de conexi\u00f3n.", "snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible." + }, + "flow_title": "Impresora Brother: {model} {serial_number}", + "step": { + "user": { + "data": { + "type": "Tipo de impresora" + }, + "description": "Configure la integraci\u00f3n de la impresora Brother. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/brother", + "title": "Impresora Brother" + }, + "zeroconf_confirm": { + "data": { + "type": "Tipo de impresora" + }, + "description": "\u00bfDesea agregar la Impresora Brother {model} con el n\u00famero de serie `{serial_number}` a Home Assistant?", + "title": "Impresora Brother descubierta" + } } } } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/es-419.json b/homeassistant/components/cert_expiry/translations/es-419.json index ee5fc92391f..772e37e25c8 100644 --- a/homeassistant/components/cert_expiry/translations/es-419.json +++ b/homeassistant/components/cert_expiry/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "import_failed": "La importaci\u00f3n desde la configuraci\u00f3n fall\u00f3" + }, "error": { "connection_timeout": "Tiempo de espera al conectarse a este host", "resolve_failed": "Este host no puede resolverse" diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index 9d867b0c8e7..8208e2578b0 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -28,6 +28,11 @@ "host": "Host", "port": "Puerto" } + }, + "manual_input": { + "data": { + "port": "Puerto" + } } } }, diff --git a/homeassistant/components/doorbird/translations/ca.json b/homeassistant/components/doorbird/translations/ca.json index 9bd469ee4e1..2e87939f44e 100644 --- a/homeassistant/components/doorbird/translations/ca.json +++ b/homeassistant/components/doorbird/translations/ca.json @@ -29,7 +29,7 @@ "data": { "events": "Llista d'esdeveniments separats per comes." }, - "description": "Afegeix el/s noms del/s esdeveniment/s que vulguis seguir separats per comes. Despr\u00e9s d\u2019introduir-los, utilitzeu l\u2019aplicaci\u00f3 de DoorBird per assignar-los a un esdeveniment espec\u00edfic. Consulta la documentaci\u00f3 a https://www.home-assistant.io/integrations/doorbird/#events.\nExemple: algu_ha_premut_el_boto, moviment_detectat" + "description": "Afegeix el/s noms del/s esdeveniment/s que vulguis seguir separats per comes. Despr\u00e9s d'introduir-los, utilitzeu l'aplicaci\u00f3 de DoorBird per assignar-los a un esdeveniment espec\u00edfic. Consulta la documentaci\u00f3 a https://www.home-assistant.io/integrations/doorbird/#events.\nExemple: algu_ha_premut_el_boto, moviment_detectat" } } } diff --git a/homeassistant/components/doorbird/translations/es-419.json b/homeassistant/components/doorbird/translations/es-419.json new file mode 100644 index 00000000000..1a412b38246 --- /dev/null +++ b/homeassistant/components/doorbird/translations/es-419.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "not_doorbird_device": "Este dispositivo no es un DoorBird" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "name": "Nombre del dispositivo", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "events": "Lista de eventos separados por comas." + }, + "description": "Agregue un nombre de evento separado por comas para cada evento que desee rastrear. Despu\u00e9s de ingresarlos aqu\u00ed, use la aplicaci\u00f3n DoorBird para asignarlos a un evento espec\u00edfico. Consulte la documentaci\u00f3n en https://www.home-assistant.io/integrations/doorbird/#events. Ejemplo: somebody_pressed_the_button, motion" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/es-419.json b/homeassistant/components/ecobee/translations/es-419.json index 3e19977f10f..ff9c1f53dec 100644 --- a/homeassistant/components/ecobee/translations/es-419.json +++ b/homeassistant/components/ecobee/translations/es-419.json @@ -10,6 +10,11 @@ "step": { "authorize": { "description": "Autorice esta aplicaci\u00f3n en https://www.ecobee.com/consumerportal/index.html con c\u00f3digo PIN: \n\n {pin} \n \n Luego, presione Enviar." + }, + "user": { + "data": { + "api_key": "Clave API" + } } } } diff --git a/homeassistant/components/esphome/translations/ca.json b/homeassistant/components/esphome/translations/ca.json index 6cd1405b824..9f9378081dc 100644 --- a/homeassistant/components/esphome/translations/ca.json +++ b/homeassistant/components/esphome/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP ja est\u00e0 configurat" + "already_configured": "ESP ja est\u00e0 configurat", + "already_in_progress": "La configuraci\u00f3 de l'ESP ja est\u00e0 en curs" }, "error": { "connection_error": "No s'ha pogut connectar amb ESP. Verifica que l'arxiu YAML cont\u00e9 la l\u00ednia 'api:'.", diff --git a/homeassistant/components/esphome/translations/cs.json b/homeassistant/components/esphome/translations/cs.json index b8c245a66e4..36a600befaa 100644 --- a/homeassistant/components/esphome/translations/cs.json +++ b/homeassistant/components/esphome/translations/cs.json @@ -1,14 +1,33 @@ { "config": { + "abort": { + "already_configured": "Tento ESP uzel je ji\u017e nakonfigurov\u00e1n", + "already_in_progress": "Konfigurace uzlu ESP ji\u017e prob\u00edh\u00e1" + }, + "error": { + "connection_error": "Nelze se p\u0159ipojit k ESP. Zkontrolujte, zda va\u0161e YAML konfigurace obsahuje \u0159\u00e1dek 'api:'.", + "invalid_password": "Neplatn\u00e9 heslo", + "resolve_error": "Nelze naj\u00edt IP adresu uzlu ESP. Pokud tato chyba p\u0159etrv\u00e1v\u00e1, nastavte statickou adresu IP: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips" + }, + "flow_title": "ESPHome: {name}", "step": { "authenticate": { - "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name} ." + "data": { + "password": "Heslo" + }, + "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name} .", + "title": "Zadejte heslo" }, "discovery_confirm": { "description": "Chcete do domovsk\u00e9ho asistenta p\u0159idat uzel ESPHome `{name}`?", "title": "Nalezen uzel ESPHome" }, "user": { + "data": { + "host": "Adresa uzlu", + "port": "Port" + }, + "description": "Zadejte pros\u00edm nastaven\u00ed p\u0159ipojen\u00ed va\u0161eho [ESPHome](https://esphomelib.com/) uzlu.", "title": "[%key:component::esphome::title%]" } } diff --git a/homeassistant/components/esphome/translations/fr.json b/homeassistant/components/esphome/translations/fr.json index 8159b3cac2e..a6620218f0e 100644 --- a/homeassistant/components/esphome/translations/fr.json +++ b/homeassistant/components/esphome/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "ESP est d\u00e9j\u00e0 configur\u00e9", + "already_in_progress": "La configuration ESP est d\u00e9j\u00e0 en cours" }, "error": { "connection_error": "Impossible de se connecter \u00e0 ESP. Assurez-vous que votre fichier YAML contient une ligne 'api:'.", diff --git a/homeassistant/components/esphome/translations/pl.json b/homeassistant/components/esphome/translations/pl.json index c74b3d57e93..276a0b404dd 100644 --- a/homeassistant/components/esphome/translations/pl.json +++ b/homeassistant/components/esphome/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP jest ju\u017c skonfigurowane." + "already_configured": "ESP jest ju\u017c skonfigurowane.", + "already_in_progress": "Konfiguracja ESP jest ju\u017c w toku." }, "error": { "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z ESP. Upewnij si\u0119, \u017ce Tw\u00f3j plik YAML zawiera lini\u0119 'api:'.", diff --git a/homeassistant/components/esphome/translations/ru.json b/homeassistant/components/esphome/translations/ru.json index 8361fa371a4..d9407b1c20e 100644 --- a/homeassistant/components/esphome/translations/ru.json +++ b/homeassistant/components/esphome/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f." }, "error": { "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a ESP. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 YAML-\u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 'api:'.", diff --git a/homeassistant/components/esphome/translations/zh-Hant.json b/homeassistant/components/esphome/translations/zh-Hant.json index 7495204945d..3657af88ce9 100644 --- a/homeassistant/components/esphome/translations/zh-Hant.json +++ b/homeassistant/components/esphome/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "ESP \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "ESP \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002" }, "error": { "connection_error": "\u7121\u6cd5\u9023\u7dda\u81f3 ESP\uff0c\u8acb\u78ba\u5b9a\u60a8\u7684 YAML \u6a94\u6848\u5305\u542b\u300capi:\u300d\u8a2d\u5b9a\u5217\u3002", diff --git a/homeassistant/components/gios/translations/ca.json b/homeassistant/components/gios/translations/ca.json index 1e451432be7..29703281b08 100644 --- a/homeassistant/components/gios/translations/ca.json +++ b/homeassistant/components/gios/translations/ca.json @@ -14,7 +14,7 @@ "name": "Nom de la integraci\u00f3", "station_id": "ID de l'estaci\u00f3 de mesura" }, - "description": "Integraci\u00f3 de mesura de qualitat de l\u2019aire GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Si necessites ajuda amb la configuraci\u00f3, fes un cop d'ull a: https://www.home-assistant.io/integrations/gios", + "description": "Integraci\u00f3 de mesura de qualitat de l'aire GIO\u015a (Polish Chief Inspectorate Of Environmental Protection). Si necessites ajuda amb la configuraci\u00f3, fes un cop d'ull a: https://www.home-assistant.io/integrations/gios", "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } diff --git a/homeassistant/components/glances/translations/ca.json b/homeassistant/components/glances/translations/ca.json index dd9d151296f..7da63024f8b 100644 --- a/homeassistant/components/glances/translations/ca.json +++ b/homeassistant/components/glances/translations/ca.json @@ -27,7 +27,7 @@ "step": { "init": { "data": { - "scan_interval": "Freq\u00fc\u00e8ncia d\u2019actualitzaci\u00f3" + "scan_interval": "Freq\u00fc\u00e8ncia d'actualitzaci\u00f3" }, "description": "Opcions de configuraci\u00f3 de Glances" } diff --git a/homeassistant/components/harmony/translations/ca.json b/homeassistant/components/harmony/translations/ca.json index a7c520c400c..90d8f064301 100644 --- a/homeassistant/components/harmony/translations/ca.json +++ b/homeassistant/components/harmony/translations/ca.json @@ -26,8 +26,8 @@ "step": { "init": { "data": { - "activity": "Activitat predeterminada a executar quan no se n\u2019especifica cap.", - "delay_secs": "Retard entre l\u2019enviament d\u2019ordres." + "activity": "Activitat predeterminada a executar quan no se n'especifica cap.", + "delay_secs": "Retard entre l'enviament d'ordres." }, "description": "Ajusta les opcions de Harmony Hub" } diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json new file mode 100644 index 00000000000..63cf68c99d1 --- /dev/null +++ b/homeassistant/components/homekit/translations/ca.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Ja hi ha un enlla\u00e7 configurat amb aquest nom o port." + }, + "step": { + "pairing": { + "description": "Tan aviat com l'enlla\u00e7 {name} estigui llest, rebr\u00e0s una \"Notificaci\u00f3\" de configuraci\u00f3 de l'enlla\u00e7 HomeKit informan-te de que la vinculaci\u00f3 est\u00e0 disponible.", + "title": "Vinculaci\u00f3 de l'enlla\u00e7 HomeKit" + }, + "user": { + "data": { + "auto_start": "Autoarrencada (desactiva-ho si fas servir Z-Wave o algun altre sistema d'inici lent)", + "include_domains": "Dominis a incloure" + }, + "description": "L'enlla\u00e7 HomeKit et permet accedir a les teves entitats de Home Assistant directament a HomeKit. Aquests enll\u00e7os 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, \u00e9s recomanable que utilitzis diferents enlla\u00e7os HomeKit per a dominis diferents. La configuraci\u00f3 avan\u00e7ada d'entitat per l'enlla\u00e7 prinipal nom\u00e9s est\u00e0 disponible amb YAML.", + "title": "Activaci\u00f3 de l'enlla\u00e7 HomeKit" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "[%key::component::homekit::config::step::user::data::auto_start%]", + "safe_mode": "Mode segur (habilita-ho nom\u00e9s si falla la vinculaci\u00f3)", + "zeroconf_default_interface": "Utilitza la interf\u00edcie zeroconf predeterminada. Activa-ho si no es pot trobar l'enlla\u00e7 a l'aplicaci\u00f3 Casa (Home app)." + }, + "description": "Aquests par\u00e0metres nom\u00e9s s'han d'ajustar si l'enlla\u00e7 HomeKit no \u00e9s funcional.", + "title": "Configuraci\u00f3 avan\u00e7ada" + }, + "exclude": { + "data": { + "exclude_entities": "Entitats a excloure" + }, + "description": "Selecciona les entitats que NO vulguis que siguin enlla\u00e7ades.", + "title": "Exclusi\u00f3 d'entitats de l'enlla\u00e7 en dominis seleccionats" + }, + "init": { + "data": { + "include_domains": "[%key::component::homekit::config::step::user::data::include_domains%]" + }, + "description": "Les entitats a \"Dominis a incloure\" s'enlla\u00e7aran a HomeKit. A la seg\u00fcent pantalla podr\u00e0s seleccionar quines entitats vols excloure d'aquesta llista.", + "title": "Selecci\u00f3 dels dominis a enlla\u00e7ar." + }, + "yaml": { + "description": "Aquesta entrada es controla en YAML", + "title": "Ajusta les opcions de l'enlla\u00e7 HomeKit" + } + } + }, + "title": "Enlla\u00e7 HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/cs.json b/homeassistant/components/homekit/translations/cs.json new file mode 100644 index 00000000000..3e96cd44af8 --- /dev/null +++ b/homeassistant/components/homekit/translations/cs.json @@ -0,0 +1,3 @@ +{ + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index ca5a67f5363..8d43341c61b 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -1,54 +1,53 @@ { - "title" : "HomeKit Bridge", - "options" : { - "step" : { - "yaml" : { - "title" : "Adjust HomeKit Bridge Options", - "description" : "This entry is controlled via YAML" - }, - "init" : { - "data" : { - "include_domains" : "[%key:component::homekit::config::step::user::data::include_domains%]" - }, - "description" : "Entities in the “Domains to include” will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", - "title" : "Select domains to bridge." - }, - "exclude" : { - "data" : { - "exclude_entities" : "Entities to exclude" - }, - "description" : "Choose the entities that you do NOT want to be bridged.", - "title" : "Exclude entities in selected domains from bridge" - }, - "advanced" : { - "data" : { - "auto_start" : "[%key:component::homekit::config::step::user::data::auto_start%]", - "safe_mode" : "Safe Mode (enable only if pairing fails)", - "zeroconf_default_interface" : "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" - }, - "description" : "These settings only need to be adjusted if the HomeKit bridge is not functional.", - "title" : "Advanced Configuration" - } - } - }, - "config" : { - "step" : { - "user" : { - "data" : { - "auto_start" : "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains" : "Domains to include" - }, - "description" : "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", - "title" : "Activate HomeKit Bridge" - }, - "pairing": { - "title": "Pair HomeKit Bridge", - "description": "As soon as the {name} bridge is ready, pairing will be available in “Notifications” as “HomeKit Bridge Setup”." - } - }, - "abort" : { - "port_name_in_use" : "A bridge with the same name or port is already configured." - } - } - } - \ No newline at end of file + "config": { + "abort": { + "port_name_in_use": "A bridge with the same name or port is already configured." + }, + "step": { + "pairing": { + "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d.", + "title": "Pair HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include" + }, + "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title": "Activate HomeKit Bridge" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "safe_mode": "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + }, + "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title": "Advanced Configuration" + }, + "exclude": { + "data": { + "exclude_entities": "Entities to exclude" + }, + "description": "Choose the entities that you do NOT want to be bridged.", + "title": "Exclude entities in selected domains from bridge" + }, + "init": { + "data": { + "include_domains": "Domains to include" + }, + "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title": "Select domains to bridge." + }, + "yaml": { + "description": "This entry is controlled via YAML", + "title": "Adjust HomeKit Bridge Options" + } + } + }, + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json new file mode 100644 index 00000000000..4e746d4c15c --- /dev/null +++ b/homeassistant/components/homekit/translations/fr.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Une passerelle avec le m\u00eame nom ou port est d\u00e9j\u00e0 configur\u00e9e." + }, + "step": { + "pairing": { + "description": "D\u00e8s que le pont {name} est pr\u00eat, l'appairage sera disponible dans \"Notifications\" sous \"Configuration de la Passerelle HomeKit\".", + "title": "Appairage de la Passerelle Homekit" + }, + "user": { + "data": { + "auto_start": "D\u00e9marrage automatique (d\u00e9sactiver si vous utilisez Z-Wave ou un autre syst\u00e8me de d\u00e9marrage diff\u00e9r\u00e9)", + "include_domains": "Domaines \u00e0 inclure" + }, + "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" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "D\u00e9marrage automatique (d\u00e9sactiver si vous utilisez Z-Wave ou un autre syst\u00e8me de d\u00e9marrage diff\u00e9r\u00e9)", + "safe_mode": "Mode sans \u00e9chec (activez uniquement si le jumelage \u00e9choue)", + "zeroconf_default_interface": "Utiliser l'interface zeroconf par d\u00e9faut (activer si le pont est introuvable dans l'application Home)" + }, + "description": "Ces param\u00e8tres ne doivent \u00eatre ajust\u00e9s que si le pont HomeKit n'est pas fonctionnel.", + "title": "Configuration avanc\u00e9e" + }, + "exclude": { + "data": { + "exclude_entities": "Entit\u00e9s \u00e0 exclure" + }, + "description": "Choisissez les entit\u00e9s que vous ne souhaitez PAS voir reli\u00e9es.", + "title": "Exclure les entit\u00e9s des domaines s\u00e9lectionn\u00e9s de la passerelle" + }, + "init": { + "data": { + "include_domains": "Domaine \u00e0 inclure" + }, + "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.", + "title": "S\u00e9lectionnez les domaines \u00e0 relier." + }, + "yaml": { + "description": "Cette entr\u00e9e est contr\u00f4l\u00e9e via YAML", + "title": "Ajuster les options de la passerelle HomeKit" + } + } + }, + "title": "Passerelle HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/pl.json b/homeassistant/components/homekit/translations/pl.json new file mode 100644 index 00000000000..6aacc0deed6 --- /dev/null +++ b/homeassistant/components/homekit/translations/pl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Mostek o okre\u015blonej nazwie lub adresie IP jest ju\u017c skonfigurowany." + }, + "step": { + "pairing": { + "title": "Sparuj z mostkiem HomeKit" + }, + "user": { + "data": { + "include_domains": "Domeny do uwzgl\u0119dnienia" + }, + "title": "Aktywowanie mostka HomeKit" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "safe_mode": "Tryb awaryjny (w\u0142\u0105cz tylko wtedy, gdy parowanie nie powiedzie si\u0119)" + } + }, + "init": { + "title": "Domeny do uwzgl\u0119dnienia." + }, + "yaml": { + "description": "Ten wpis jest kontrolowany przez YAML", + "title": "Dostosowywanie opcji mostka HomeKit" + } + } + }, + "title": "Mostek HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json new file mode 100644 index 00000000000..00bd865ad9e --- /dev/null +++ b/homeassistant/components/homekit/translations/ru.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0436\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c \u0438\u043b\u0438 \u043f\u043e\u0440\u0442\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + }, + "step": { + "pairing": { + "description": "\u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0440\u0438\u0434\u0436 {name} \u0431\u0443\u0434\u0435\u0442 \u0433\u043e\u0442\u043e\u0432, \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\".", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", + "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" + }, + "description": "HomeKit Bridge \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0432\u0430\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0432 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 \u043c\u043e\u0441\u0442. \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 \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0431\u0440\u0438\u0434\u0436\u0430.", + "title": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c HomeKit Bridge" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "\u0410\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a (\u043e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Z-Wave \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430)", + "safe_mode": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c (\u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u0431\u043e\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f)", + "zeroconf_default_interface": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c zeroconf \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e (\u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435, \u0435\u0441\u043b\u0438 \u0431\u0440\u0438\u0434\u0436 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0439\u0434\u0435\u043d \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 '\u0414\u043e\u043c')." + }, + "description": "\u042d\u0442\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b, \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 HomeKit Bridge \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.", + "title": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430" + }, + "exclude": { + "data": { + "exclude_entities": "\u0418\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442\u044b" + }, + "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u041d\u0415 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432 HomeKit.", + "title": "\u0418\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u0430\u0445" + }, + "init": { + "data": { + "include_domains": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b" + }, + "description": "\u041e\u0431\u044a\u0435\u043a\u0442\u044b, \u043f\u0440\u0438\u043d\u0430\u0434\u043b\u0435\u0436\u0430\u0449\u0438\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u043c \u0434\u043e\u043c\u0435\u043d\u0430\u043c, \u0431\u0443\u0434\u0443\u0442 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u044b \u0432 HomeKit. \u041d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u043c \u044d\u0442\u0430\u043f\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0412\u044b \u0441\u043c\u043e\u0436\u0435\u0442\u0435 \u0432\u044b\u0431\u0440\u0430\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0438\u0441\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0438\u0437 \u044d\u0442\u0438\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432.", + "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" + }, + "yaml": { + "description": "\u042d\u0442\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0447\u0435\u0440\u0435\u0437 YAML", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 HomeKit Bridge" + } + } + }, + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/zh-Hant.json b/homeassistant/components/homekit/translations/zh-Hant.json new file mode 100644 index 00000000000..ea53860a91a --- /dev/null +++ b/homeassistant/components/homekit/translations/zh-Hant.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "\u4f7f\u7528\u76f8\u540c\u540d\u7a31\u6216\u901a\u8a0a\u57e0\u7684 Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + }, + "step": { + "pairing": { + "description": "\u65bc {name} bridge \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", + "title": "\u914d\u5c0d HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", + "include_domains": "\u5305\u542b Domain" + }, + "description": "HomeKit Bridge \u5c07\u53ef\u5141\u8a31\u65bc Homekit \u4e2d\u4f7f\u7528 Home Assistant \u7269\u4ef6\u3002HomeKit 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 Domain \u4f7f\u7528\u591a\u500b HomeKit bridges \u9054\u5230\u6b64\u9700\u6c42\u3002\u50c5\u80fd\u65bc\u4e3b Bridge \u4ee5 YAML \u8a2d\u5b9a\u8a73\u7d30\u7269\u4ef6\u3002", + "title": "\u555f\u7528 HomeKit Bridge" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "\u81ea\u52d5\u555f\u52d5\uff08\u5047\u5982\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u9072\u555f\u52d5\u7cfb\u7d71\u6642\u3001\u8acb\u95dc\u9589\uff09", + "safe_mode": "\u5b89\u5168\u6a21\u5f0f\uff08\u50c5\u65bc\u914d\u5c0d\u5931\u6557\u6642\u4f7f\u7528\uff09", + "zeroconf_default_interface": "\u4f7f\u7528\u9810\u8a2d zeroconf \u4ecb\u9762\uff08\u50c5\u65bc\u5bb6\u5ead App \u627e\u4e0d\u5230 Bridge \u6642\u958b\u555f\uff09" + }, + "description": "\u50c5\u65bc Homekit bridge \u7121\u6cd5\u6b63\u5e38\u4f7f\u7528\u6642\uff0c\u8abf\u6574\u6b64\u4e9b\u8a2d\u5b9a\u3002", + "title": "\u9032\u968e\u8a2d\u5b9a" + }, + "exclude": { + "data": { + "exclude_entities": "\u6392\u9664\u7269\u4ef6" + }, + "description": "\u9078\u64c7\u4e0d\u9032\u884c\u6a4b\u63a5\u7684\u7269\u4ef6\u3002", + "title": "\u65bc\u6240\u9078 Domain \u4e2d\u6240\u8981\u6392\u9664\u7684\u7269\u4ef6" + }, + "init": { + "data": { + "include_domains": "\u5305\u542b Domain" + }, + "description": "\u300c\u5305\u542b Domain\u300d\u4e2d\u7684\u7269\u4ef6\u5c07\u6703\u6a4b\u63a5\u81f3 Homekit\u3001\u53ef\u4ee5\u65bc\u4e0b\u4e00\u500b\u756b\u9762\u4e2d\u9078\u64c7\u6240\u8981\u5305\u542b\u6216\u6392\u9664\u7684\u7269\u4ef6\u5217\u8868\u3002", + "title": "\u9078\u64c7\u6240\u8981\u6a4b\u63a5\u7684 Domain\u3002" + }, + "yaml": { + "description": "\u6b64\u7269\u4ef6\u70ba\u900f\u904e YAML \u63a7\u5236", + "title": "\u8abf\u6574 HomeKit Bridge \u9078\u9805" + } + } + }, + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ca.json b/homeassistant/components/homekit_controller/translations/ca.json index db174f049b8..3407d93c63f 100644 --- a/homeassistant/components/homekit_controller/translations/ca.json +++ b/homeassistant/components/homekit_controller/translations/ca.json @@ -13,7 +13,7 @@ "authentication_error": "Codi HomeKit incorrecte. Verifica'l i torna-ho a provar.", "busy_error": "El dispositiu ha refusat la vinculaci\u00f3 perqu\u00e8 actualment ho est\u00e0 intentant amb un altre controlador diferent.", "max_peers_error": "El dispositiu ha refusat la vinculaci\u00f3 perqu\u00e8 no t\u00e9 suficient espai lliure.", - "max_tries_error": "El dispositiu ha refusat la vinculaci\u00f3 perqu\u00e8 ha rebut m\u00e9s de 100 intents d\u2019autenticaci\u00f3 fallits.", + "max_tries_error": "El dispositiu ha refusat la vinculaci\u00f3 perqu\u00e8 ha rebut m\u00e9s de 100 intents d'autenticaci\u00f3 fallits.", "pairing_failed": "S'ha produ\u00eft un error mentre s'intentava la vinculaci\u00f3 amb el dispositiu. Pot ser que sigui un error temporal o pot ser que el teu dispositiu encara no estigui suportat.", "unable_to_pair": "No s'ha pogut vincular, torna-ho a provar.", "unknown_error": "El dispositiu ha em\u00e8s un error desconegut. Vinculaci\u00f3 fallida." diff --git a/homeassistant/components/huawei_lte/translations/ca.json b/homeassistant/components/huawei_lte/translations/ca.json index 762132a459a..cb8a4331a57 100644 --- a/homeassistant/components/huawei_lte/translations/ca.json +++ b/homeassistant/components/huawei_lte/translations/ca.json @@ -23,7 +23,7 @@ "url": "URL", "username": "Nom d'usuari" }, - "description": "Introdueix les dades d\u2019acc\u00e9s del dispositiu. El nom d\u2019usuari i contrasenya s\u00f3n opcionals, per\u00f2 habiliten m\u00e9s funcions de la integraci\u00f3. D'altra banda, (mentre la integraci\u00f3 estigui activa) l'\u00fas d'una connexi\u00f3 autoritzada pot causar problemes per accedir a la interf\u00edcie web del dispositiu des de fora de Home Assistant i viceversa.", + "description": "Introdueix les dades d'acc\u00e9s del dispositiu. El nom d'usuari i contrasenya s\u00f3n opcionals, per\u00f2 habiliten m\u00e9s funcions de la integraci\u00f3. D'altra banda, (mentre la integraci\u00f3 estigui activa) l'\u00fas d'una connexi\u00f3 autoritzada pot causar problemes per accedir a la interf\u00edcie web del dispositiu des de fora de Home Assistant i viceversa.", "title": "Configuraci\u00f3 de Huawei LTE" } } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/fr.json b/homeassistant/components/hunterdouglas_powerview/translations/fr.json new file mode 100644 index 00000000000..a1bd06078c6 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/fr.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", + "unknown": "Erreur inattendue" + }, + "step": { + "link": { + "description": "Voulez-vous configurer {name} ({host})?", + "title": "Connectez-vous au concentrateur PowerView" + }, + "user": { + "data": { + "host": "Adresse IP" + }, + "title": "Connectez-vous au concentrateur PowerView" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/pl.json b/homeassistant/components/hunterdouglas_powerview/translations/pl.json new file mode 100644 index 00000000000..7e535d0d270 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/pl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." + }, + "step": { + "link": { + "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?", + "title": "Po\u0142\u0105cz si\u0119 z hubem PowerView" + }, + "user": { + "data": { + "host": "Adres IP" + }, + "title": "Po\u0142\u0105cz si\u0119 z hubem PowerView" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/ca.json b/homeassistant/components/icloud/translations/ca.json index ade0c4a3bd3..5fa4ee6626d 100644 --- a/homeassistant/components/icloud/translations/ca.json +++ b/homeassistant/components/icloud/translations/ca.json @@ -5,7 +5,7 @@ "no_device": "Cap dels teus dispositius t\u00e9 activada la opci\u00f3 \"Troba el meu iPhone\"" }, "error": { - "login": "Error d\u2019inici de sessi\u00f3: comprova el correu electr\u00f2nic i la contrasenya", + "login": "Error d'inici de sessi\u00f3: comprova el correu electr\u00f2nic i la contrasenya", "send_verification_code": "No s'ha pogut enviar el codi de verificaci\u00f3", "validate_verification_code": "No s'ha pogut verificar el codi de verificaci\u00f3, tria un dispositiu de confian\u00e7a i torna a iniciar el proc\u00e9s" }, diff --git a/homeassistant/components/konnected/translations/ca.json b/homeassistant/components/konnected/translations/ca.json index afe65f67f62..d35146410ed 100644 --- a/homeassistant/components/konnected/translations/ca.json +++ b/homeassistant/components/konnected/translations/ca.json @@ -11,7 +11,7 @@ }, "step": { "confirm": { - "description": "Model: {model} \nAmfitri\u00f3: {host} \nPort: {port} \n\nPots configurar el comportament de les E/S (I/O) i del panell a la configuraci\u00f3 del panell d\u2019alarma Konnected.", + "description": "Model: {model} \nID: {id}\nAmfitri\u00f3: {host} \nPort: {port} \n\nPots configurar el comportament de les E/S (I/O) i del panell a la configuraci\u00f3 del panell d'alarma Konnected.", "title": "Dispositiu Konnected llest" }, "import_confirm": { diff --git a/homeassistant/components/life360/translations/ca.json b/homeassistant/components/life360/translations/ca.json index a61e4372b78..09cefc0bce1 100644 --- a/homeassistant/components/life360/translations/ca.json +++ b/homeassistant/components/life360/translations/ca.json @@ -19,7 +19,7 @@ "password": "Contrasenya", "username": "Nom d'usuari" }, - "description": "Per configurar les opcions avan\u00e7ades mira la [documentaci\u00f3 de Life360]({docs_url}). Pot ser que ho hagis de fer abans d\u2019afegir cap compte.", + "description": "Per configurar les opcions avan\u00e7ades mira la [documentaci\u00f3 de Life360]({docs_url}). Pot ser que ho hagis de fer abans d'afegir cap compte.", "title": "Informaci\u00f3 del compte Life360" } } diff --git a/homeassistant/components/linky/translations/ca.json b/homeassistant/components/linky/translations/ca.json index 127d2870ae7..954b873083a 100644 --- a/homeassistant/components/linky/translations/ca.json +++ b/homeassistant/components/linky/translations/ca.json @@ -7,7 +7,7 @@ "access": "No s'ha pogut accedir a Enedis.fr, comprova la teva connexi\u00f3 a Internet", "enedis": "Enedis.fr ha respost amb un error: torna-ho a provar m\u00e9s tard (millo no entre les 23:00 i les 14:00)", "unknown": "Error desconegut: torna-ho a provar m\u00e9s tard (millor no entre les 23:00 i les 14:00)", - "wrong_login": "Error d\u2019inici de sessi\u00f3: comprova el teu correu electr\u00f2nic i la contrasenya" + "wrong_login": "Error d'inici de sessi\u00f3: comprova el teu correu electr\u00f2nic i la contrasenya" }, "step": { "user": { diff --git a/homeassistant/components/logi_circle/translations/ca.json b/homeassistant/components/logi_circle/translations/ca.json index 0aa0db1dc9a..8b81f752058 100644 --- a/homeassistant/components/logi_circle/translations/ca.json +++ b/homeassistant/components/logi_circle/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "Nom\u00e9s pots configurar un \u00fanic compte de Logi Circule.", - "external_error": "S'ha produ\u00eft una excepci\u00f3 d\u2019un altre flux de dades.", + "external_error": "S'ha produ\u00eft una excepci\u00f3 d'un altre flux de dades.", "external_setup": "Logi Circle s'ha configurat correctament des d'un altre flux de dades.", "no_flows": "Necessites configurar Logi Circle abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/logi_circle/)." }, @@ -10,8 +10,8 @@ "default": "Autenticaci\u00f3 exitosa amb Logi Circle." }, "error": { - "auth_error": "Ha fallat l\u2019autoritzaci\u00f3 de l\u2019API.", - "auth_timeout": "L\u2019autoritzaci\u00f3 ha expirat durant l'obtenci\u00f3 del token d\u2019acc\u00e9s.", + "auth_error": "Ha fallat l'autoritzaci\u00f3 de l'API.", + "auth_timeout": "L'autoritzaci\u00f3 ha expirat durant l'obtenci\u00f3 del token d'acc\u00e9s.", "follow_link": "V\u00e9s a l'enlla\u00e7 i autentica't abans de pr\u00e9mer Envia" }, "step": { diff --git a/homeassistant/components/melcloud/translations/ca.json b/homeassistant/components/melcloud/translations/ca.json index 2c3bec09790..92472d020c4 100644 --- a/homeassistant/components/melcloud/translations/ca.json +++ b/homeassistant/components/melcloud/translations/ca.json @@ -14,7 +14,7 @@ "password": "Contrasenya de MELCloud.", "username": "Correu electr\u00f2nic d'inici de sessi\u00f3 a MELCloud." }, - "description": "Connecta\u2019t amb el teu compte de MELCloud.", + "description": "Connecta't amb el teu compte de MELCloud.", "title": "Connexi\u00f3 amb MELCloud" } } diff --git a/homeassistant/components/minecraft_server/translations/ca.json b/homeassistant/components/minecraft_server/translations/ca.json index e34e2fb7fdb..6e5554216d9 100644 --- a/homeassistant/components/minecraft_server/translations/ca.json +++ b/homeassistant/components/minecraft_server/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3 amb el servidor. Comprova l'amfitri\u00f3 i el port i torna-ho a provar. Assegurat que estas utilitzant la versi\u00f3 del servidor 1.7 o superior.", - "invalid_ip": "L\u2019adre\u00e7a IP \u00e9s inv\u00e0lida (no s\u2019ha pogut determinar l\u2019adre\u00e7a MAC). Corregeix-la i torna-ho a provar.", + "invalid_ip": "L'adre\u00e7a IP \u00e9s inv\u00e0lida (no s'ha pogut determinar l'adre\u00e7a MAC). Corregeix-la i torna-ho a provar.", "invalid_port": "El port ha d'estar compr\u00e8s entre 1024 i 65535. Corregeix-lo i torna-ho a provar." }, "step": { diff --git a/homeassistant/components/mobile_app/translations/ca.json b/homeassistant/components/mobile_app/translations/ca.json index 697f676df78..84e613ca978 100644 --- a/homeassistant/components/mobile_app/translations/ca.json +++ b/homeassistant/components/mobile_app/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "install_app": "Obre l\u2019aplicaci\u00f3 m\u00f2bil per configurar la integraci\u00f3 amb Home Assistant. Mira [la documentaci\u00f3]({apps_url}) per veure la llista d\u2019aplicacions compatibles." + "install_app": "Obre l'aplicaci\u00f3 m\u00f2bil per configurar la integraci\u00f3 amb Home Assistant. Mira [la documentaci\u00f3]({apps_url}) per veure la llista d'aplicacions compatibles." }, "step": { "confirm": { diff --git a/homeassistant/components/nuheat/translations/ca.json b/homeassistant/components/nuheat/translations/ca.json index 165d889dbdc..c7821838f4f 100644 --- a/homeassistant/components/nuheat/translations/ca.json +++ b/homeassistant/components/nuheat/translations/ca.json @@ -16,7 +16,7 @@ "serial_number": "N\u00famero de s\u00e8rie del term\u00f2stat.", "username": "Nom d'usuari" }, - "description": "Has d\u2019obtenir el n\u00famero de s\u00e8rie o identificador del teu term\u00f2stat entrant a https://MyNuHeat.com i seleccionant el teu term\u00f2stat.", + "description": "Has d'obtenir el n\u00famero de s\u00e8rie o identificador del teu term\u00f2stat entrant a https://MyNuHeat.com i seleccionant el teu term\u00f2stat.", "title": "Connexi\u00f3 amb NuHeat" } } diff --git a/homeassistant/components/onvif/translations/ca.json b/homeassistant/components/onvif/translations/ca.json new file mode 100644 index 00000000000..9d942f091d4 --- /dev/null +++ b/homeassistant/components/onvif/translations/ca.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositiu ONVIF ja configurat.", + "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ONVIF ja est\u00e0 en curs.", + "no_h264": "No s'han torbat fluxos (streams) H264 disponibles. Comporva la configuraci\u00f3 de perfil en el dispositiu.", + "no_mac": "No s'ha pogut configurar un ID \u00fanic pel dispositiu ONVIF.", + "onvif_error": "Error durant la configuraci\u00f3 del dispositiu ONVIF. Consulta els registres per a m\u00e9s informaci\u00f3." + }, + "error": { + "connection_failed": "No s'ha pogut connectar al servei ONVIF amb les credencials proporcionades." + }, + "step": { + "auth": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "title": "Configuraci\u00f3 d'autenticaci\u00f3" + }, + "configure_profile": { + "data": { + "include": "Crea entitat de c\u00e0mera" + }, + "description": "Crear entitat de c\u00e0mera per {profile} amb resoluci\u00f3 {resolution}?", + "title": "Configuraci\u00f3 dels perfils" + }, + "device": { + "data": { + "host": "Selecciona un dispositiu ONVIF descobert" + }, + "title": "Selecci\u00f3 de dispositiu ONVIF" + }, + "manual_input": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port" + }, + "title": "Configura el dispositiu ONVIF" + }, + "user": { + "description": "En fer clic a envia, es cercaran a la xarxa dispositius ONVIF que suportin perfils S.\n\nAlguns fabricants han comen\u00e7at a desactivar ONVIF per defecte. Comprova que ONVIF est\u00e0 activat a la configuraci\u00f3 de les c\u00e0meres.", + "title": "Configuraci\u00f3 de dispositiu ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Arguments addicionals FFMPEG", + "rtsp_transport": "Mecanisme de transport RTSP" + }, + "title": "Opcions de dispositiu ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/cs.json b/homeassistant/components/onvif/translations/cs.json new file mode 100644 index 00000000000..dad373fb5e3 --- /dev/null +++ b/homeassistant/components/onvif/translations/cs.json @@ -0,0 +1,56 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed ONVIF je ji\u017e nakonfigurov\u00e1no", + "no_h264": "Nebyly k dispozici \u017e\u00e1dn\u00e9 H264 streamy. Zkontrolujte konfiguraci profilu v za\u0159\u00edzen\u00ed.", + "no_mac": "Nelze nakonfigurovat jedine\u010dn\u00e9 ID pro za\u0159\u00edzen\u00ed ONVIF.", + "onvif_error": "P\u0159i nastavov\u00e1n\u00ed za\u0159\u00edzen\u00ed ONVIF do\u0161lo k chyb\u011b. Dal\u0161\u00ed informace naleznete v protokolech." + }, + "error": { + "connection_failed": "Nelze se p\u0159ipojit ke slu\u017eb\u011b ONVIF s poskytnut\u00fdmi p\u0159ihla\u0161ovac\u00edmi \u00fadaji." + }, + "step": { + "auth": { + "data": { + "password": "Heslo", + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" + }, + "title": "Konfigurace ov\u011b\u0159ov\u00e1n\u00ed" + }, + "configure_profile": { + "data": { + "include": "Vytvo\u0159it entitu kamery" + }, + "description": "Chcete vytvo\u0159it entitu kamery pro {profile} s rozli\u0161en\u00edm {resolution}?", + "title": "Nakonfigurovat profily" + }, + "device": { + "data": { + "host": "Vyberte nalezen\u00e1 za\u0159\u00edzen\u00ed ONVIF" + }, + "title": "Vyberte za\u0159\u00edzen\u00ed ONVIF" + }, + "manual_input": { + "data": { + "host": "Adresa za\u0159\u00edzen\u00ed", + "port": "Port" + }, + "title": "Konfigurovat za\u0159\u00edzen\u00ed ONVIF" + }, + "user": { + "description": "Kliknut\u00edm na tla\u010d\u00edtko Odeslat vyhled\u00e1me ve va\u0161\u00ed s\u00edti za\u0159\u00edzen\u00ed ONVIF, kter\u00e1 podporuj\u00ed profil S. \n\nN\u011bkte\u0159\u00ed v\u00fdrobci vypli funkci ONVIF v z\u00e1kladn\u00edm nastaven\u00ed. Ujist\u011bte se, \u017ee je v konfiguraci kamery povolena funkce ONVIF.", + "title": "Nastaven\u00ed za\u0159\u00edzen\u00ed ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Dal\u0161\u00ed FFMPEG argumenty" + }, + "title": "Mo\u017enosti za\u0159\u00edzen\u00ed ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/en.json b/homeassistant/components/onvif/translations/en.json index 066efed60f2..91828648cc0 100644 --- a/homeassistant/components/onvif/translations/en.json +++ b/homeassistant/components/onvif/translations/en.json @@ -54,6 +54,5 @@ "title": "ONVIF Device Options" } } - }, - "title": "ONVIF" + } } \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/fr.json b/homeassistant/components/onvif/translations/fr.json new file mode 100644 index 00000000000..38036b65517 --- /dev/null +++ b/homeassistant/components/onvif/translations/fr.json @@ -0,0 +1,57 @@ +{ + "config": { + "abort": { + "already_configured": "Le p\u00e9riph\u00e9rique ONVIF est d\u00e9j\u00e0 configur\u00e9.", + "already_in_progress": "Le flux de configuration pour le p\u00e9riph\u00e9rique ONVIF est d\u00e9j\u00e0 en cours.", + "no_h264": "Aucun flux H264 n'\u00e9tait disponible. V\u00e9rifiez la configuration du profil sur votre appareil.", + "onvif_error": "Erreur lors de la configuration du p\u00e9riph\u00e9rique ONVIF. Consultez les journaux pour plus d'informations." + }, + "error": { + "connection_failed": "Impossible de se connecter au service ONVIF avec les informations d'identification fournies." + }, + "step": { + "auth": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "title": "Configurer l'authentification" + }, + "configure_profile": { + "data": { + "include": "Cr\u00e9er une entit\u00e9 cam\u00e9ra" + }, + "description": "Cr\u00e9er une entit\u00e9 cam\u00e9ra pour {profile} \u00e0 la r\u00e9solution {resolution} ?", + "title": "Configurer les profils" + }, + "device": { + "data": { + "host": "S\u00e9lectionnez le p\u00e9riph\u00e9rique ONVIF d\u00e9couvert" + }, + "title": "S\u00e9lectionnez l'appareil ONVIF" + }, + "manual_input": { + "data": { + "host": "H\u00f4te", + "port": "Port" + }, + "title": "Configurer l\u2019appareil ONVIF" + }, + "user": { + "description": "En cliquant sur soumettre, nous rechercherons sur votre r\u00e9seau, des \u00e9quipements ONVIF qui supporte le Profile S.\n\nCertains constructeurs ont commenc\u00e9 \u00e0 d\u00e9sactiver ONvif par d\u00e9faut. Assurez vous que ONVIF est activ\u00e9 dans la configuration de votre cam\u00e9ra", + "title": "Configuration de l'appareil ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Arguments FFMPEG suppl\u00e9mentaires", + "rtsp_transport": "M\u00e9canisme de transport RTSP" + }, + "title": "Options ONVIF de l'appareil" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/no.json b/homeassistant/components/onvif/translations/no.json new file mode 100644 index 00000000000..8ad30a8bf77 --- /dev/null +++ b/homeassistant/components/onvif/translations/no.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "manual_input": { + "data": { + "host": "Vert", + "port": "" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json new file mode 100644 index 00000000000..99a17d48df0 --- /dev/null +++ b/homeassistant/components/onvif/translations/pl.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie ONVIF jest ju\u017c skonfigurowane.", + "already_in_progress": "Proces konfiguracji dla urz\u0105dzenia ONVIF jest ju\u017c w toku.", + "no_h264": "Nie by\u0142o dost\u0119pnych \u017cadnych strumieni H264. Sprawd\u017a konfiguracj\u0119 profilu w swoim urz\u0105dzeniu.", + "no_mac": "Nie mo\u017cna utworzy\u0107 unikalnego identyfikatora urz\u0105dzenia ONVIF.", + "onvif_error": "Wyst\u0105pi\u0142 b\u0142\u0105d podczas konfigurowania urz\u0105dzenia ONVIF. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji." + }, + "error": { + "connection_failed": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z us\u0142ug\u0105 ONVIF z podanymi po\u015bwiadczeniami." + }, + "step": { + "auth": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Konfigurowanie uwierzytelniania" + }, + "configure_profile": { + "data": { + "include": "Utw\u00f3rz encj\u0119 kamery" + }, + "description": "Czy utworzy\u0107 encj\u0119 kamery dla {profile} o rozdzielczo\u015bci {resolution}?", + "title": "Konfigurowanie profili" + }, + "device": { + "data": { + "host": "Wybierz odnalezione urz\u0105dzenie ONVIF" + }, + "title": "Wybierz urz\u0105dzenie ONVIF" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Konfigurowanie urz\u0105dzenia ONVIF" + }, + "user": { + "description": "Klikaj\u0105c przycisk Prze\u015blij, Twoja sie\u0107 zostanie przeszukana pod k\u0105tem urz\u0105dze\u0144 ONVIF obs\u0142uguj\u0105cych profil S.\n\nNiekt\u00f3rzy producenci zacz\u0119li domy\u015blnie wy\u0142\u0105cza\u0107 ONVIF. Upewnij si\u0119, \u017ce ONVIF jest w\u0142\u0105czony w konfiguracji kamery.", + "title": "Konfiguracja urz\u0105dzenia ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Dodatkowe argumenty FFMPEG", + "rtsp_transport": "Mechanizm transportu RTSP" + }, + "title": "Opcje urz\u0105dzenia ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json new file mode 100644 index 00000000000..20b709a4a01 --- /dev/null +++ b/homeassistant/components/onvif/translations/ru.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "no_h264": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 H264. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0412\u0430\u0448\u0435\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435.", + "no_mac": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", + "onvif_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." + }, + "error": { + "connection_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u043b\u0443\u0436\u0431\u0435 ONVIF \u0441 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438." + }, + "step": { + "auth": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "title": "\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f" + }, + "configure_profile": { + "data": { + "include": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u044b" + }, + "description": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 \u043a\u0430\u043c\u0435\u0440\u044b \u0434\u043b\u044f {profile} \u0441 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c {resolution}?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0444\u0438\u043b\u0435\u0439" + }, + "device": { + "data": { + "host": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e ONVIF" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e ONVIF" + }, + "manual_input": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 ONVIF" + }, + "user": { + "description": "\u041a\u043e\u0433\u0434\u0430 \u0412\u044b \u043d\u0430\u0436\u043c\u0451\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c, \u043d\u0430\u0447\u043d\u0451\u0442\u0441\u044f \u043f\u043e\u0438\u0441\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 ONVIF, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442 Profile S.\n\n\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u0438 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043e\u0442\u043a\u043b\u044e\u0447\u0430\u044e\u0442 ONVIF. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e ONVIF \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445 \u0412\u0430\u0448\u0435\u0439 \u043a\u0430\u043c\u0435\u0440\u044b.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b FFMPEG", + "rtsp_transport": "\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u044b\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c RTSP" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/zh-Hant.json b/homeassistant/components/onvif/translations/zh-Hant.json new file mode 100644 index 00000000000..c7a0f88d1b9 --- /dev/null +++ b/homeassistant/components/onvif/translations/zh-Hant.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "ONVIF \u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", + "already_in_progress": "ONVIF \u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "no_h264": "\u8a72\u8a2d\u5099\u4e0d\u652f\u63f4 H264 \u4e32\u6d41\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5099\u8a2d\u5b9a\u3002", + "no_mac": "\u7121\u6cd5\u70ba ONVIF \u8a2d\u5099\u8a2d\u5b9a\u552f\u4e00 ID\u3002", + "onvif_error": "\u8a2d\u5b9a ONVIF \u8a2d\u5099\u932f\u8aa4\uff0c\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8cc7\u8a0a\u3002" + }, + "error": { + "connection_failed": "\u7121\u6cd5\u4ee5\u6240\u63d0\u4f9b\u7684\u6191\u8b49\u9023\u7dda\u81f3 ONVIF \u670d\u52d9\u3002" + }, + "step": { + "auth": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "title": "\u8a2d\u5b9a\u9a57\u8b49" + }, + "configure_profile": { + "data": { + "include": "\u65b0\u589e\u651d\u5f71\u6a5f\u7269\u4ef6" + }, + "description": "\u4ee5 {profile} \u4f7f\u7528\u89e3\u6790\u5ea6 {resolution} \u65b0\u589e\u651d\u5f71\u6a5f\u7269\u4ef6\uff1f", + "title": "\u8a2d\u5b9a Profiles" + }, + "device": { + "data": { + "host": "\u9078\u64c7\u6240\u63a2\u7d22\u5230\u7684 ONVIF \u8a2d\u5099" + }, + "title": "\u9078\u64c7 ONVIF \u8a2d\u5099" + }, + "manual_input": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0" + }, + "title": "\u8a2d\u5b9a ONVIF \u8a2d\u5099" + }, + "user": { + "description": "\u9ede\u4e0b\u50b3\u9001\u5f8c\u3001\u5c07\u6703\u641c\u5c0b\u7db2\u8def\u4e2d\u652f\u63f4 Profile S \u7684 ONVIF \u8a2d\u5099\u3002\n\n\u67d0\u4e9b\u5ee0\u5546\u9810\u8a2d\u7684\u6a21\u5f0f\u70ba ONVIF \u95dc\u9589\u6a21\u5f0f\uff0c\u8acb\u518d\u6b21\u78ba\u8a8d\u651d\u5f71\u6a5f\u5df2\u7d93\u958b\u555f ONVIF\u3002", + "title": "ONVIF \u8a2d\u5099\u8a2d\u5b9a" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "\u984d\u5916 FFMPEG \u53c3\u6578", + "rtsp_transport": "RTSP \u50b3\u8f38\u5354\u5b9a" + }, + "title": "ONVIF \u8a2d\u5099\u9078\u9805" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/ca.json b/homeassistant/components/opentherm_gw/translations/ca.json index 41a155b147f..6660c93767e 100644 --- a/homeassistant/components/opentherm_gw/translations/ca.json +++ b/homeassistant/components/opentherm_gw/translations/ca.json @@ -24,7 +24,7 @@ "floor_temperature": "Temperatura de la planta", "precision": "Precisi\u00f3" }, - "description": "Opcions del la passarel\u00b7la d'enlla\u00e7 d\u2019OpenTherm" + "description": "Opcions del la passarel\u00b7la d'enlla\u00e7 d'OpenTherm" } } } diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index 83b3e7e7e55..712fa6f251d 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Tots els servidors enlla\u00e7ats ja estan configurats", "already_configured": "Aquest servidor Plex ja est\u00e0 configurat", - "already_in_progress": "S\u2019est\u00e0 configurant Plex", + "already_in_progress": "S'est\u00e0 configurant Plex", "invalid_import": "La configuraci\u00f3 importada \u00e9s inv\u00e0lida", "non-interactive": "Importaci\u00f3 no interactiva", "token_request_timeout": "S'ha acabat el temps d'espera durant l'obtenci\u00f3 del token.", diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index 2416a2bf7f2..b0764b78234 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -6,7 +6,7 @@ "error": { "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", "unknown": "Error inesperat", - "wrong_version": "El teu Powerwall utilitza una versi\u00f3 de programari no compatible. L'hauries d'actualitzar o informar d\u2019aquest problema perqu\u00e8 sigui solucionat." + "wrong_version": "El teu Powerwall utilitza una versi\u00f3 de programari no compatible. L'hauries d'actualitzar o informar d'aquest problema perqu\u00e8 sigui solucionat." }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json index a96fa3b584b..8eeab499620 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json @@ -9,7 +9,7 @@ "name": "Nom del sensor", "tariff": "Tarifa contractada (1, 2 o 3 per\u00edodes)" }, - "description": "Aquest sensor utilitza l'API oficial de la xarxa el\u00e8ctrica espanyola (REE) per obtenir els [preus per hora de l\u2019electricitat (PVPC)](https://www.esios.ree.es/es/pvpc) a Espanya.\nPer a m\u00e9s informaci\u00f3, consulta la [documentaci\u00f3 de la integraci\u00f3](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecciona la tarifa contractada, cadascuna t\u00e9 un nombre determinat de per\u00edodes: \n - 1 per\u00edode: normal (sense discriminaci\u00f3)\n - 2 per\u00edodes: discriminaci\u00f3 (tarifa nocturna) \n - 3 per\u00edodes: cotxe el\u00e8ctric (tarifa nocturna de 3 per\u00edodes)", + "description": "Aquest sensor utilitza l'API oficial de la xarxa el\u00e8ctrica espanyola (REE) per obtenir els [preus per hora de l'electricitat (PVPC)](https://www.esios.ree.es/es/pvpc) a Espanya.\nPer a m\u00e9s informaci\u00f3, consulta la [documentaci\u00f3 de la integraci\u00f3](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecciona la tarifa contractada, cadascuna t\u00e9 un nombre determinat de per\u00edodes: \n - 1 per\u00edode: normal (sense discriminaci\u00f3)\n - 2 per\u00edodes: discriminaci\u00f3 (tarifa nocturna) \n - 3 per\u00edodes: cotxe el\u00e8ctric (tarifa nocturna de 3 per\u00edodes)", "title": "Selecci\u00f3 de tarifa" } } diff --git a/homeassistant/components/rachio/translations/ca.json b/homeassistant/components/rachio/translations/ca.json index 8d4f9aa7245..14b4d8effd8 100644 --- a/homeassistant/components/rachio/translations/ca.json +++ b/homeassistant/components/rachio/translations/ca.json @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "manual_run_mins": "Durant quant de temps (en minuts) mantenir engegada una estaci\u00f3 quan l\u2019interruptor s'activa." + "manual_run_mins": "Durant quant de temps (en minuts) mantenir engegada una estaci\u00f3 quan l'interruptor s'activa." } } } diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index f6f09dab4ee..1704fa04897 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -3,14 +3,14 @@ "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.", - "auth_missing": "A Home Assistant nem jogosult csatlakozni ehhez a Samsung TV-hez. Ellen\u0151rizze a TV-k\u00e9sz\u00fcl\u00e9k\u00e9ben a Home Assistant enged\u00e9lyez\u00e9si be\u00e1ll\u00edt\u00e1sait.", + "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.", "not_successful": "Nem lehet csatlakozni ehhez a Samsung TV k\u00e9sz\u00fcl\u00e9khez.", "not_supported": "Ez a Samsung TV k\u00e9sz\u00fcl\u00e9k jelenleg nem t\u00e1mogatott." }, "flow_title": "Samsung TV: {model}", "step": { "confirm": { - "description": "Be\u00e1ll\u00edtja a Samsung TV {model} k\u00e9sz\u00fcl\u00e9ket? Ha soha nem csatlakozott home assistant-hez ezel\u0151tt, meg kell jelennie egy felugr\u00f3 ablaknak a TV-ben, ahol hiteles\u00edt\u00e9st k\u00e9r. A tv-k\u00e9sz\u00fcl\u00e9k manu\u00e1lis konfigur\u00e1ci\u00f3i fel\u00fcl\u00edr\u00f3dnak.", + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Samsung TV {model} k\u00e9sz\u00fcl\u00e9ket? Ha m\u00e9g soha nem csatlakozott Home Assistant-hez, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ahol hiteles\u00edt\u00e9st k\u00e9r. A TV manu\u00e1lis konfigur\u00e1ci\u00f3i fel\u00fcl\u00edr\u00f3dnak.", "title": "Samsung TV" }, "user": { @@ -18,7 +18,7 @@ "host": "Hosztn\u00e9v vagy IP c\u00edm", "name": "N\u00e9v" }, - "description": "\u00cdrja be a Samsung TV adatait. Ha soha nem csatlakoztatta a Home Assistant alkalmaz\u00e1st ezel\u0151tt, l\u00e1tnia kell a t\u00e9v\u00e9ben egy felugr\u00f3 ablakot, amely enged\u00e9lyt k\u00e9r.", + "description": "\u00cdrd be a Samsung TV adatait. Ha m\u00e9g soha nem csatlakozott Home Assistant-hez, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ahol hiteles\u00edt\u00e9st k\u00e9r.", "title": "Samsung TV" } } diff --git a/homeassistant/components/smartthings/translations/ca.json b/homeassistant/components/smartthings/translations/ca.json index f5a7dacf01b..9e37cb0d945 100644 --- a/homeassistant/components/smartthings/translations/ca.json +++ b/homeassistant/components/smartthings/translations/ca.json @@ -19,7 +19,7 @@ "data": { "access_token": "Token d'acc\u00e9s" }, - "description": "Introdueix un [token d'acc\u00e9s personal]({token_url}) d'SmartThings creat a partir de les [instruccions]({component_url}). S\u2019utilitzar\u00e0 per crear la integraci\u00f3 de Home Assistant dins el teu compte de SmartThings.", + "description": "Introdueix un [token d'acc\u00e9s personal]({token_url}) d'SmartThings creat a partir de les [instruccions]({component_url}). S'utilitzar\u00e0 per crear la integraci\u00f3 de Home Assistant dins el teu compte de SmartThings.", "title": "Introdueix el token d'acc\u00e9s personal" }, "select_location": { diff --git a/homeassistant/components/solaredge/translations/ca.json b/homeassistant/components/solaredge/translations/ca.json index 56e1633e3a1..ca5d472c9d6 100644 --- a/homeassistant/components/solaredge/translations/ca.json +++ b/homeassistant/components/solaredge/translations/ca.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "api_key": "Clau API d\u2019aquest lloc", - "name": "Nom d\u2019aquesta instal\u00b7laci\u00f3", + "api_key": "Clau API d'aquest lloc", + "name": "Nom d'aquesta instal\u00b7laci\u00f3", "site_id": "SolarEdge site_id" }, "title": "Configuraci\u00f3 dels par\u00e0metres de l'API per aquesta instal\u00b7laci\u00f3" diff --git a/homeassistant/components/starline/translations/ca.json b/homeassistant/components/starline/translations/ca.json index d8c76856480..722b65da2a8 100644 --- a/homeassistant/components/starline/translations/ca.json +++ b/homeassistant/components/starline/translations/ca.json @@ -34,7 +34,7 @@ "username": "Nom d'usuari" }, "description": "Correu electr\u00f2nic i contrasenya del compte StarLine", - "title": "Credencials d\u2019usuari" + "title": "Credencials d'usuari" } } } diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index f593ac26182..b5e1a847380 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "connection": "Error de connexi\u00f3: comprova l'amfitri\u00f3, la contrasenya i l'SSL", - "login": "Error d\u2019inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya", + "login": "Error d'inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya", "missing_data": "Falten dades: torna-ho a provar m\u00e9s tard o prova una altra configuraci\u00f3 diferent", "otp_failed": "L'autenticaci\u00f3 en dos passos ha fallat, torna-ho a provar amb un nou codi", "unknown": "Error desconegut: consulta els registres per a m\u00e9s detalls." diff --git a/homeassistant/components/totalconnect/translations/ca.json b/homeassistant/components/totalconnect/translations/ca.json index 19106050c6d..ca9e6d66e2e 100644 --- a/homeassistant/components/totalconnect/translations/ca.json +++ b/homeassistant/components/totalconnect/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El compte ja ha estat configurat" }, "error": { - "login": "Error d\u2019inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya" + "login": "Error d'inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya" }, "step": { "user": { diff --git a/homeassistant/components/transmission/translations/ca.json b/homeassistant/components/transmission/translations/ca.json index 837766ba6ed..e2b014ac019 100644 --- a/homeassistant/components/transmission/translations/ca.json +++ b/homeassistant/components/transmission/translations/ca.json @@ -25,7 +25,7 @@ "step": { "init": { "data": { - "scan_interval": "Freq\u00fc\u00e8ncia d\u2019actualitzaci\u00f3" + "scan_interval": "Freq\u00fc\u00e8ncia d'actualitzaci\u00f3" }, "title": "Opcions de configuraci\u00f3 de Transmission" } diff --git a/homeassistant/components/transmission/translations/es-419.json b/homeassistant/components/transmission/translations/es-419.json new file mode 100644 index 00000000000..6a01b3e25a1 --- /dev/null +++ b/homeassistant/components/transmission/translations/es-419.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Frecuencia de actualizaci\u00f3n" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/es-419.json b/homeassistant/components/twentemilieu/translations/es-419.json index ed333bb9b51..5cc3dc4b2c9 100644 --- a/homeassistant/components/twentemilieu/translations/es-419.json +++ b/homeassistant/components/twentemilieu/translations/es-419.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "house_number": "N\u00famero de casa", + "post_code": "C\u00f3digo postal" + }, "title": "Twente Milieu" } } diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index 6d8b395f2b0..6bd5e84bb6f 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -28,7 +28,7 @@ "client_control": { "data": { "block_client": "Clients controlats amb acc\u00e9s a la xarxa", - "new_client": "Afegeix un client nou per al control d\u2019acc\u00e9s a la xarxa", + "new_client": "Afegeix un client nou per al control d'acc\u00e9s a la xarxa", "poe_clients": "Permet control POE dels clients" }, "description": "Configura els controls del client \n\nConfigura interruptors per als n\u00fameros de s\u00e8rie als quals vulguis controlar l'acc\u00e9s a la xarxa.", @@ -45,11 +45,19 @@ "description": "Configuraci\u00f3 de seguiment de dispositius", "title": "Opcions d'UniFi" }, + "simple_options": { + "data": { + "block_client": "[%key::component::unifi::options::step::client_control::data::block_client%]", + "track_clients": "[%key::component::unifi::options::step::device_tracker::data::track_clients%]", + "track_devices": "[%key::component::unifi::options::step::device_tracker::data::track_devices%]" + }, + "description": "Configura la integraci\u00f3 d'UniFi" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Crea sensors d'\u00fas d'ample de banda per a clients de la xarxa" }, - "description": "Configuraci\u00f3 dels sensors d\u2019estad\u00edstiques", + "description": "Configuraci\u00f3 dels sensors d'estad\u00edstiques", "title": "Opcions d'UniFi" } } diff --git a/homeassistant/components/unifi/translations/cs.json b/homeassistant/components/unifi/translations/cs.json index c28ca26919c..8fe1c060992 100644 --- a/homeassistant/components/unifi/translations/cs.json +++ b/homeassistant/components/unifi/translations/cs.json @@ -24,6 +24,13 @@ }, "options": { "step": { + "simple_options": { + "data": { + "track_clients": "Sledov\u00e1n\u00ed p\u0159ipojen\u00fdch za\u0159\u00edzen\u00ed", + "track_devices": "Sledov\u00e1n\u00ed s\u00ed\u0165ov\u00fdch za\u0159\u00edzen\u00ed (za\u0159\u00edzen\u00ed Ubiquiti)" + }, + "description": "Konfigurace integrace UniFi" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Vytvo\u0159it senzory vyu\u017eit\u00ed \u0161\u00ed\u0159ky p\u00e1sma pro s\u00ed\u0165ov\u00e9 klienty" diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index fd7096686e1..e49bbbcc50e 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -48,9 +48,9 @@ }, "simple_options": { "data": { - "track_clients": "[%key:component::unifi::options::step::device_tracker::data::track_clients%]", - "track_devices": "[%key:component::unifi::options::step::device_tracker::data::track_devices%]", - "block_client": "[%key:component::unifi::options::step::client_control::data::block_client%]" + "block_client": "Network access controlled clients", + "track_clients": "Track network clients", + "track_devices": "Track network devices (Ubiquiti devices)" }, "description": "Configure UniFi integration" }, diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index 007f06b1c8e..851613d6997 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -48,6 +48,13 @@ "other": "Vide" } }, + "simple_options": { + "data": { + "track_clients": "Suivi de clients r\u00e9seaux", + "track_devices": "Suivi d'\u00e9quipement r\u00e9seau (Equipements Ubiquiti)" + }, + "description": "Configurer l'int\u00e9gration UniFi" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Cr\u00e9er des capteurs d'utilisation de la bande passante pour les clients r\u00e9seau" diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 9f7b1ca58b6..b145aa2802c 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -54,6 +54,9 @@ "other": "Inne" } }, + "simple_options": { + "description": "Konfigurowanie integracji z UniFi" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Stw\u00f3rz sensory wykorzystania przepustowo\u015bci przez klient\u00f3w sieciowych" diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index ae3e5c6e3f4..54a8857f19c 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -53,6 +53,14 @@ "other": "\u0434\u0440\u0443\u0433\u0438\u0435" } }, + "simple_options": { + "data": { + "block_client": "\u041a\u043b\u0438\u0435\u043d\u0442\u044b \u0441 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0435\u043c \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430", + "track_clients": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0441\u0435\u0442\u0438", + "track_devices": "\u041e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 (\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Ubiquiti)" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 UniFi." + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u044b \u043f\u043e\u043b\u043e\u0441\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432" diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index 7247293662a..3f1946ac725 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -46,6 +46,14 @@ "description": "\u8a2d\u5b9a\u8a2d\u5099\u8ffd\u8e64", "title": "UniFi \u9078\u9805 1/3" }, + "simple_options": { + "data": { + "block_client": "\u7db2\u8def\u5b58\u53d6\u63a7\u5236\u5ba2\u6236\u7aef", + "track_clients": "\u8ffd\u8e64\u7db2\u8def\u5ba2\u6236\u7aef", + "track_devices": "\u8ffd\u8e64\u7db2\u8def\u8a2d\u5099\uff08Ubiquiti \u8a2d\u5099\uff09" + }, + "description": "\u8a2d\u5b9a UniFi \u6574\u5408" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "\u7db2\u8def\u5ba2\u6236\u7aef\u983b\u5bec\u7528\u91cf\u611f\u61c9\u5668" diff --git a/homeassistant/components/vesync/translations/ca.json b/homeassistant/components/vesync/translations/ca.json index 6dbf41d9ef2..cdeeaa3ed57 100644 --- a/homeassistant/components/vesync/translations/ca.json +++ b/homeassistant/components/vesync/translations/ca.json @@ -12,7 +12,7 @@ "password": "Contrasenya", "username": "Correu electr\u00f2nic" }, - "title": "Introdueix el nom d\u2019usuari i contrasenya" + "title": "Introdueix el nom d'usuari i contrasenya" } } } diff --git a/homeassistant/components/vilfo/translations/es-419.json b/homeassistant/components/vilfo/translations/es-419.json new file mode 100644 index 00000000000..524b7e7b934 --- /dev/null +++ b/homeassistant/components/vilfo/translations/es-419.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Conectar con el Router Vilfo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ca.json b/homeassistant/components/vizio/translations/ca.json index 6f2b98e053f..1dec17b885b 100644 --- a/homeassistant/components/vizio/translations/ca.json +++ b/homeassistant/components/vizio/translations/ca.json @@ -5,7 +5,7 @@ "updated_entry": "Aquesta entrada ja s'ha configurat per\u00f2 el nom i les opcions definides a la configuraci\u00f3 no coincideixen amb els valors importats anteriorment, en conseq\u00fc\u00e8ncia, s'han actualitzat." }, "error": { - "cant_connect": "No s'ha pogut connectar amb el dispositiu. [Comprova la documentaci\u00f3](https://www.home-assistant.io/integrations/vizio/) i torna a verificar que: \n - El dispositiu est\u00e0 engegat \n - El dispositiu est\u00e0 connectat a la xarxa \n - Els valors que has intridu\u00eft s\u00f3n correctes\n abans d\u2019intentar tornar a presentar.", + "cant_connect": "No s'ha pogut connectar amb el dispositiu. [Comprova la documentaci\u00f3](https://www.home-assistant.io/integrations/vizio/) i torna a verificar que: \n - El dispositiu est\u00e0 engegat \n - El dispositiu est\u00e0 connectat a la xarxa \n - Els valors que has intridu\u00eft s\u00f3n correctes\n abans d'intentar tornar a enviar.", "complete_pairing failed": "No s'ha pogut completar l'emparellament. Verifica que el PIN proporcionat sigui el correcte i que el televisor segueix connectat a la xarxa abans de provar-ho de nou.", "host_exists": "Dispositiu Vizio amb aquest nom d'amfitri\u00f3 ja configurat.", "name_exists": "Dispositiu Vizio amb aquest nom ja configurat." diff --git a/homeassistant/components/vizio/translations/es-419.json b/homeassistant/components/vizio/translations/es-419.json new file mode 100644 index 00000000000..b8dc207c47b --- /dev/null +++ b/homeassistant/components/vizio/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device_class": "Tipo de dispositivo", + "name": "Nombre" + }, + "title": "Configurar el dispositivo VIZIO SmartCast" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "apps_to_include_or_exclude": "Aplicaciones para incluir o excluir", + "include_or_exclude": "\u00bfIncluir o excluir aplicaciones?" + }, + "title": "Actualizar las opciones de VIZIO SmartCast" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/ca.json b/homeassistant/components/withings/translations/ca.json index 3b2ee2eff9b..e98426446fc 100644 --- a/homeassistant/components/withings/translations/ca.json +++ b/homeassistant/components/withings/translations/ca.json @@ -15,7 +15,7 @@ "data": { "profile": "Perfil" }, - "description": "Quin perfil has seleccionat al lloc web de Withings? \u00c9s important que els perfils coincideixin sin\u00f3, les dades no s\u2019etiquetaran correctament.", + "description": "Quin perfil has seleccionat al lloc web de Withings? \u00c9s important que els perfils coincideixin sin\u00f3, les dades no s'etiquetaran correctament.", "title": "Perfil d'usuari." } } diff --git a/homeassistant/components/xiaomi_miio/translations/cs.json b/homeassistant/components/xiaomi_miio/translations/cs.json new file mode 100644 index 00000000000..d784fda2c0d --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/cs.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nakonfigurov\u00e1no" + }, + "error": { + "connect_error": "Nepoda\u0159ilo se p\u0159ipojit, zkuste to znovu", + "no_device_selected": "Nebylo vybr\u00e1no \u017e\u00e1dn\u00e9 za\u0159\u00edzen\u00ed, vyberte jedno za\u0159\u00edzen\u00ed." + }, + "step": { + "gateway": { + "data": { + "host": "IP adresa", + "name": "N\u00e1zev br\u00e1ny", + "token": "API Token" + }, + "description": "Je vy\u017eadov\u00e1n token API, pokyny naleznete na str\u00e1nce https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token.", + "title": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" + }, + "user": { + "data": { + "gateway": "P\u0159ipojen\u00ed k br\u00e1n\u011b Xiaomi" + }, + "description": "Vyberte, ke kter\u00e9mu za\u0159\u00edzen\u00ed se chcete p\u0159ipojit.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file From 842e09923af27d49244f25822af0038c5b7b735f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 May 2020 22:34:43 -0500 Subject: [PATCH 205/511] Cleanup homekit strings spacing (#35056) --- homeassistant/components/homekit/strings.json | 105 +++++++++--------- .../components/homekit/translations/en.json | 70 ++++++------ 2 files changed, 87 insertions(+), 88 deletions(-) diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index ca5a67f5363..08ebfa44c45 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -1,54 +1,53 @@ { - "title" : "HomeKit Bridge", - "options" : { - "step" : { - "yaml" : { - "title" : "Adjust HomeKit Bridge Options", - "description" : "This entry is controlled via YAML" - }, - "init" : { - "data" : { - "include_domains" : "[%key:component::homekit::config::step::user::data::include_domains%]" - }, - "description" : "Entities in the “Domains to include” will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", - "title" : "Select domains to bridge." - }, - "exclude" : { - "data" : { - "exclude_entities" : "Entities to exclude" - }, - "description" : "Choose the entities that you do NOT want to be bridged.", - "title" : "Exclude entities in selected domains from bridge" - }, - "advanced" : { - "data" : { - "auto_start" : "[%key:component::homekit::config::step::user::data::auto_start%]", - "safe_mode" : "Safe Mode (enable only if pairing fails)", - "zeroconf_default_interface" : "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" - }, - "description" : "These settings only need to be adjusted if the HomeKit bridge is not functional.", - "title" : "Advanced Configuration" - } - } - }, - "config" : { - "step" : { - "user" : { - "data" : { - "auto_start" : "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains" : "Domains to include" - }, - "description" : "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", - "title" : "Activate HomeKit Bridge" - }, - "pairing": { - "title": "Pair HomeKit Bridge", - "description": "As soon as the {name} bridge is ready, pairing will be available in “Notifications” as “HomeKit Bridge Setup”." - } - }, - "abort" : { - "port_name_in_use" : "A bridge with the same name or port is already configured." - } - } - } - \ No newline at end of file + "title": "HomeKit Bridge", + "options": { + "step": { + "yaml": { + "title": "Adjust HomeKit Bridge Options", + "description": "This entry is controlled via YAML" + }, + "init": { + "data": { + "include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]" + }, + "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title": "Select domains to bridge." + }, + "exclude": { + "data": { + "exclude_entities": "Entities to exclude" + }, + "description": "Choose the entities that you do NOT want to be bridged.", + "title": "Exclude entities in selected domains from bridge" + }, + "advanced": { + "data": { + "auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]", + "safe_mode": "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + }, + "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title": "Advanced Configuration" + } + } + }, + "config": { + "step": { + "user": { + "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include" + }, + "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title": "Activate HomeKit Bridge" + }, + "pairing": { + "title": "Pair HomeKit Bridge", + "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d." + } + }, + "abort": { + "port_name_in_use": "A bridge with the same name or port is already configured." + } + } +} diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 8d43341c61b..08ebfa44c45 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -1,33 +1,17 @@ { - "config": { - "abort": { - "port_name_in_use": "A bridge with the same name or port is already configured." - }, - "step": { - "pairing": { - "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d.", - "title": "Pair HomeKit Bridge" - }, - "user": { - "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains": "Domains to include" - }, - "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", - "title": "Activate HomeKit Bridge" - } - } - }, + "title": "HomeKit Bridge", "options": { "step": { - "advanced": { + "yaml": { + "title": "Adjust HomeKit Bridge Options", + "description": "This entry is controlled via YAML" + }, + "init": { "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "safe_mode": "Safe Mode (enable only if pairing fails)", - "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + "include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]" }, - "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", - "title": "Advanced Configuration" + "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title": "Select domains to bridge." }, "exclude": { "data": { @@ -36,18 +20,34 @@ "description": "Choose the entities that you do NOT want to be bridged.", "title": "Exclude entities in selected domains from bridge" }, - "init": { + "advanced": { "data": { - "include_domains": "Domains to include" + "auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]", + "safe_mode": "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" }, - "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", - "title": "Select domains to bridge." - }, - "yaml": { - "description": "This entry is controlled via YAML", - "title": "Adjust HomeKit Bridge Options" + "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title": "Advanced Configuration" } } }, - "title": "HomeKit Bridge" -} \ No newline at end of file + "config": { + "step": { + "user": { + "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include" + }, + "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title": "Activate HomeKit Bridge" + }, + "pairing": { + "title": "Pair HomeKit Bridge", + "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d." + } + }, + "abort": { + "port_name_in_use": "A bridge with the same name or port is already configured." + } + } +} From 187392deec5f06d482dd236b6c5d05ddba2aaff2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 1 May 2020 21:00:45 -0700 Subject: [PATCH 206/511] Bump hass-nabucasa to 0.34.2 (#35046) --- 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 9b2541eedd0..de5496cfd99 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.34.1"], + "requirements": ["hass-nabucasa==0.34.2"], "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 938427eadd4..c888f44c173 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.1 +hass-nabucasa==0.34.2 home-assistant-frontend==20200427.1 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index ee8d53035c7..fe439d5c73d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -686,7 +686,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.1 +hass-nabucasa==0.34.2 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fb7716dd38c..f1d80a3c9f0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -279,7 +279,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.1 +hass-nabucasa==0.34.2 # homeassistant.components.mqtt hbmqtt==0.9.5 From 963236916c3b84186da820105945e21e0d77de51 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 1 May 2020 23:01:15 -0500 Subject: [PATCH 207/511] Fix another race in august tests (#35054) --- tests/components/august/mocks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 0e1e9866c1d..c471dfca2a9 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -107,11 +107,11 @@ async def _create_august_with_devices( def lock_return_activities_side_effect(access_token, device_id): lock = _get_device_detail("locks", device_id) return [ - _mock_lock_operation_activity(lock, "lock", 0), # There is a check to prevent out of order events - # so we set the doorclosed event in the future + # so we set the doorclosed & lock event in the future # to prevent a race condition where we reject the event - # because it happened before the dooropen event. + # because it happened before the dooropen & unlock event. + _mock_lock_operation_activity(lock, "lock", 2000), _mock_door_operation_activity(lock, "doorclosed", 2000), ] From d9b9a004d221a5de86d4b96da5e98541d054e6a1 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Fri, 1 May 2020 22:01:29 -0600 Subject: [PATCH 208/511] Fix Canary doing I/O in the event loop (#35039) --- homeassistant/components/canary/camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 1631038f81a..870256ffcff 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -81,7 +81,7 @@ class CanaryCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" - self.renew_live_stream_session() + await self.hass.async_add_executor_job(self.renew_live_stream_session) ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) image = await asyncio.shield( From 0519b96bec79b2db2ec8021aca1149288e2b659a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 2 May 2020 00:49:52 -0700 Subject: [PATCH 209/511] Add scene to default config (#35058) --- homeassistant/components/default_config/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/default_config/manifest.json b/homeassistant/components/default_config/manifest.json index d324ac862e3..0b80e172904 100644 --- a/homeassistant/components/default_config/manifest.json +++ b/homeassistant/components/default_config/manifest.json @@ -12,6 +12,7 @@ "map", "mobile_app", "person", + "scene", "script", "ssdp", "sun", From 5a7d38b28c8b93f594b49348b480aacab6650e11 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Sat, 2 May 2020 09:19:39 -0700 Subject: [PATCH 210/511] Bump roombapy to 1.5.2 (#35067) --- homeassistant/components/roomba/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index a164509bc99..b9e521ffc12 100644 --- a/homeassistant/components/roomba/manifest.json +++ b/homeassistant/components/roomba/manifest.json @@ -3,7 +3,7 @@ "name": "iRobot Roomba", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/roomba", - "requirements": ["roombapy==1.5.1"], + "requirements": ["roombapy==1.5.2"], "dependencies": [], "codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"] } diff --git a/requirements_all.txt b/requirements_all.txt index fe439d5c73d..b1347e7582a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1837,7 +1837,7 @@ rocketchat-API==0.6.1 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.5.1 +roombapy==1.5.2 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f1d80a3c9f0..65433039238 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -713,7 +713,7 @@ ring_doorbell==0.6.0 roku==4.1.0 # homeassistant.components.roomba -roombapy==1.5.1 +roombapy==1.5.2 # homeassistant.components.yamaha rxv==0.6.0 From 8c84e47c94621d3fefc2f4c78068e8aae9c7d28b Mon Sep 17 00:00:00 2001 From: unixko <44964969+unixko@users.noreply.github.com> Date: Sun, 3 May 2020 00:59:23 +0700 Subject: [PATCH 211/511] added abbreviation for temperature_unit (#35076) Added temp_unit for temperature_unit. --- homeassistant/components/mqtt/abbreviations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/mqtt/abbreviations.py b/homeassistant/components/mqtt/abbreviations.py index e4262a7c548..4b310fb19ec 100644 --- a/homeassistant/components/mqtt/abbreviations.py +++ b/homeassistant/components/mqtt/abbreviations.py @@ -159,6 +159,7 @@ ABBREVIATIONS = { "temp_lo_stat_t": "temperature_low_state_topic", "temp_stat_tpl": "temperature_state_template", "temp_stat_t": "temperature_state_topic", + "temp_unit": "temperature_unit", "tilt_clsd_val": "tilt_closed_value", "tilt_cmd_t": "tilt_command_topic", "tilt_inv_stat": "tilt_invert_state", From 2f31b8576eed587597f9e9a2602ca18e359ce413 Mon Sep 17 00:00:00 2001 From: mhorst314 Date: Sat, 2 May 2020 20:30:31 +0200 Subject: [PATCH 212/511] Fix proliphix (#34397) * Retrieve the name of the Proliphix thermostat before adding the entities * The proliphix module provides hvac_state, not hvac_mode * Removed update before add_entities. Moved the setting of the name into the update function instead. * Disentangled hvac_mode and hvac_action * Ran black and isort --- homeassistant/components/proliphix/climate.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/proliphix/climate.py b/homeassistant/components/proliphix/climate.py index fc44dfba75b..5dff4725ea0 100644 --- a/homeassistant/components/proliphix/climate.py +++ b/homeassistant/components/proliphix/climate.py @@ -4,6 +4,10 @@ import voluptuous as vol from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity from homeassistant.components.climate.const import ( + CURRENT_HVAC_COOL, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF, @@ -37,6 +41,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): host = config.get(CONF_HOST) pdp = proliphix.PDP(host, username, password) + pdp.update() add_entities([ProliphixThermostat(pdp)], True) @@ -47,7 +52,7 @@ class ProliphixThermostat(ClimateEntity): def __init__(self, pdp): """Initialize the thermostat.""" self._pdp = pdp - self._name = self._pdp.name + self._name = None @property def supported_features(self): @@ -62,6 +67,7 @@ class ProliphixThermostat(ClimateEntity): def update(self): """Update the data from the thermostat.""" self._pdp.update() + self._name = self._pdp.name @property def name(self): @@ -97,16 +103,26 @@ class ProliphixThermostat(ClimateEntity): """Return the temperature we try to reach.""" return self._pdp.setback + @property + def hvac_action(self): + """Return the current state of the thermostat.""" + state = self._pdp.hvac_state + if state == 1: + return CURRENT_HVAC_OFF + if state in (3, 4, 5): + return CURRENT_HVAC_HEAT + if state in (6, 7): + return CURRENT_HVAC_COOL + return CURRENT_HVAC_IDLE + @property def hvac_mode(self): """Return the current state of the thermostat.""" - state = self._pdp.hvac_mode - if state in (1, 2): - return HVAC_MODE_OFF - if state == 3: + if self._pdp.is_heating: return HVAC_MODE_HEAT - if state == 6: + if self._pdp.is_cooling: return HVAC_MODE_COOL + return HVAC_MODE_OFF @property def hvac_modes(self): From e602de55ac09be9ab8cbb354519a1b1b57fbe362 Mon Sep 17 00:00:00 2001 From: Quentame Date: Sat, 2 May 2020 20:31:15 +0200 Subject: [PATCH 213/511] Bump python-synology to 0.8.0 + Fix disk space incorrect sensor type (#35068) * Fix Synology disk space incorrect sensor type * Review 1 --- .../components/synology_dsm/const.py | 5 +- .../components/synology_dsm/manifest.json | 2 +- .../components/synology_dsm/sensor.py | 58 ++++++++++--------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 8c17de5e997..b3c9f66c8da 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -2,6 +2,7 @@ from homeassistant.const import ( DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, + DATA_TERABYTES, UNIT_PERCENTAGE, ) @@ -34,8 +35,8 @@ UTILISATION_SENSORS = { STORAGE_VOL_SENSORS = { "volume_status": ["Status", None, "mdi:checkbox-marked-circle-outline"], "volume_device_type": ["Type", None, "mdi:harddisk"], - "volume_size_total": ["Total Size", None, "mdi:chart-pie"], - "volume_size_used": ["Used Space", None, "mdi:chart-pie"], + "volume_size_total": ["Total Size", DATA_TERABYTES, "mdi:chart-pie"], + "volume_size_used": ["Used Space", DATA_TERABYTES, "mdi:chart-pie"], "volume_percentage_used": ["Volume Used", UNIT_PERCENTAGE, "mdi:chart-pie"], "volume_disk_temp_avg": ["Average Disk Temp", None, "mdi:thermometer"], "volume_disk_temp_max": ["Maximum Disk Temp", None, "mdi:thermometer"], diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index 4a538606ecb..f57f1843f45 100644 --- a/homeassistant/components/synology_dsm/manifest.json +++ b/homeassistant/components/synology_dsm/manifest.json @@ -2,7 +2,7 @@ "domain": "synology_dsm", "name": "Synology DSM", "documentation": "https://www.home-assistant.io/integrations/synology_dsm", - "requirements": ["python-synology==0.7.4"], + "requirements": ["python-synology==0.8.0"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 6e5a486ab89..b6a88fe5a5a 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -7,11 +7,13 @@ from homeassistant.const import ( CONF_DISKS, DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, + DATA_TERABYTES, TEMP_CELSIUS, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util.temperature import celsius_to_fahrenheit from . import SynoApi from .const import ( @@ -63,7 +65,7 @@ async def async_setup_entry( class SynoNasSensor(Entity): - """Representation of a Synology NAS Sensor.""" + """Representation of a Synology NAS sensor.""" def __init__( self, @@ -142,47 +144,51 @@ class SynoNasSensor(Entity): class SynoNasUtilSensor(SynoNasSensor): - """Representation a Synology Utilisation Sensor.""" + """Representation a Synology Utilisation sensor.""" @property def state(self): """Return the state.""" - if self._unit == DATA_RATE_KILOBYTES_PER_SECOND or self._unit == DATA_MEGABYTES: - attr = getattr(self._api.utilisation, self.sensor_type)(False) + attr = getattr(self._api.utilisation, self.sensor_type) + if callable(attr): + attr = attr() + if not attr: + return None - if attr is None: - return None + # Data (RAM) + if self._unit == DATA_MEGABYTES: + return round(attr / 1024.0 ** 2, 1) - if self._unit == DATA_RATE_KILOBYTES_PER_SECOND: - return round(attr / 1024.0, 1) - if self._unit == DATA_MEGABYTES: - return round(attr / 1024.0 / 1024.0, 1) - else: - return getattr(self._api.utilisation, self.sensor_type) + # Network + if self._unit == DATA_RATE_KILOBYTES_PER_SECOND: + return round(attr / 1024.0, 1) + + return attr class SynoNasStorageSensor(SynoNasSensor): - """Representation a Synology Storage Sensor.""" + """Representation a Synology Storage sensor.""" @property def state(self): """Return the state.""" - if self.monitored_device: - if self.sensor_type in TEMP_SENSORS_KEYS: - attr = getattr(self._api.storage, self.sensor_type)( - self.monitored_device - ) + attr = getattr(self._api.storage, self.sensor_type)(self.monitored_device) + if not attr: + return None - if attr is None: - return None + # Data (disk space) + if self._unit == DATA_TERABYTES: + return round(attr / 1024.0 ** 4, 2) - if self._api.temp_unit == TEMP_CELSIUS: - return attr + # Temperature + if self._api.temp_unit == TEMP_CELSIUS: + # Celsius + return attr + if self.sensor_type in TEMP_SENSORS_KEYS: + # Fahrenheit + return celsius_to_fahrenheit(attr) - return round(attr * 1.8 + 32.0, 1) - - return getattr(self._api.storage, self.sensor_type)(self.monitored_device) - return None + return attr @property def device_info(self) -> Dict[str, any]: diff --git a/requirements_all.txt b/requirements_all.txt index b1347e7582a..9226c01a42f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1692,7 +1692,7 @@ python-sochain-api==0.0.2 python-songpal==0.11.2 # homeassistant.components.synology_dsm -python-synology==0.7.4 +python-synology==0.8.0 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 65433039238..fa570aedda9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -662,7 +662,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.synology_dsm -python-synology==0.7.4 +python-synology==0.8.0 # homeassistant.components.tado python-tado==0.8.1 From 9b59ef4fc9d349a3b846a05b38158ec9fbfbd6e0 Mon Sep 17 00:00:00 2001 From: David Nielsen Date: Sat, 2 May 2020 15:21:57 -0400 Subject: [PATCH 214/511] Update bravia-tv to 1.0.3 (#35077) --- homeassistant/components/braviatv/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json index cde236b4ca4..98c1dca08d2 100644 --- a/homeassistant/components/braviatv/manifest.json +++ b/homeassistant/components/braviatv/manifest.json @@ -2,7 +2,7 @@ "domain": "braviatv", "name": "Sony Bravia TV", "documentation": "https://www.home-assistant.io/integrations/braviatv", - "requirements": ["bravia-tv==1.0.2"], + "requirements": ["bravia-tv==1.0.3"], "codeowners": ["@robbiet480", "@bieniu"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 9226c01a42f..f1d9799fcba 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -362,7 +362,7 @@ bomradarloop==0.1.4 boto3==1.9.252 # homeassistant.components.braviatv -bravia-tv==1.0.2 +bravia-tv==1.0.3 # homeassistant.components.broadlink broadlink==0.13.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fa570aedda9..084649bbfa7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -147,7 +147,7 @@ bellows-homeassistant==0.15.2 bomradarloop==0.1.4 # homeassistant.components.braviatv -bravia-tv==1.0.2 +bravia-tv==1.0.3 # homeassistant.components.broadlink broadlink==0.13.2 From 37f3613704cf591513f60a1babda83b65d128745 Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Sat, 2 May 2020 21:24:56 +0200 Subject: [PATCH 215/511] Upgrade youtube_dl to version 2020.05.03 (#35078) --- 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 fd1d2172873..28780bb2a86 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==2020.03.24"], + "requirements": ["youtube_dl==2020.05.03"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index f1d9799fcba..e9e8fa5719f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2190,7 +2190,7 @@ yeelight==0.5.1 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2020.03.24 +youtube_dl==2020.05.03 # homeassistant.components.zengge zengge==0.2 From 88b7aba1c8009914a9ae7c4b25a2c3bb165d1b55 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 2 May 2020 21:55:11 +0200 Subject: [PATCH 216/511] Updated frontend to 20200427.2 (#35079) --- 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 9759c38af7d..0ed1697bf42 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -2,7 +2,7 @@ "domain": "frontend", "name": "Home Assistant Frontend", "documentation": "https://www.home-assistant.io/integrations/frontend", - "requirements": ["home-assistant-frontend==20200427.1"], + "requirements": ["home-assistant-frontend==20200427.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index c888f44c173..b7e70914714 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200427.1 +home-assistant-frontend==20200427.2 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index e9e8fa5719f..5882864e328 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -716,7 +716,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.1 +home-assistant-frontend==20200427.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 084649bbfa7..9c5193ff14a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -297,7 +297,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200427.1 +home-assistant-frontend==20200427.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From ab08c1bef856209fb3919b24b8216e9c61a263ad Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Sat, 2 May 2020 14:10:49 -0700 Subject: [PATCH 217/511] Fix vera config ids not being converted to integers (#35070) --- homeassistant/components/vera/__init__.py | 15 +++- homeassistant/components/vera/config_flow.py | 22 +++--- tests/components/vera/test_config_flow.py | 8 +-- tests/components/vera/test_init.py | 76 +++++++++++++++++++- 4 files changed, 103 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index 9b2d44fd94b..1e1538420b5 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -25,7 +25,7 @@ from homeassistant.util import convert, slugify from homeassistant.util.dt import utc_from_timestamp from .common import ControllerData, get_configured_platforms -from .config_flow import new_options +from .config_flow import fix_device_id_list, new_options from .const import ( ATTR_CURRENT_ENERGY_KWH, ATTR_CURRENT_POWER_W, @@ -81,9 +81,18 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ), ) + saved_light_ids = config_entry.options.get(CONF_LIGHTS, []) + saved_exclude_ids = config_entry.options.get(CONF_EXCLUDE, []) + base_url = config_entry.data[CONF_CONTROLLER] - light_ids = config_entry.options.get(CONF_LIGHTS, []) - exclude_ids = config_entry.options.get(CONF_EXCLUDE, []) + light_ids = fix_device_id_list(saved_light_ids) + exclude_ids = fix_device_id_list(saved_exclude_ids) + + # If the ids were corrected. Update the config entry. + if light_ids != saved_light_ids or exclude_ids != saved_exclude_ids: + hass.config_entries.async_update_entry( + entry=config_entry, options=new_options(light_ids, exclude_ids) + ) # Initialize the Vera controller. controller = veraApi.VeraController(base_url) diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index 3d2b30f1079..cac17951cc1 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Vera.""" import logging import re -from typing import List, cast +from typing import Any, List import pyvera as pv from requests.exceptions import RequestException @@ -17,20 +17,22 @@ LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) -def str_to_int_list(data: str) -> List[str]: +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]: """Convert a string to an int list.""" - if isinstance(str, list): - return cast(List[str], data) - - return [s for s in LIST_REGEX.split(data) if len(s) > 0] + return [int(s) for s in LIST_REGEX.split(data) if len(s) > 0] -def int_list_to_str(data: List[str]) -> 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[str], exclude: List[str]) -> dict: +def new_options(lights: List[int], exclude: List[int]) -> dict: """Create a standard options object.""" return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude} @@ -40,10 +42,10 @@ def options_schema(options: dict = None) -> dict: options = options or {} return { vol.Optional( - CONF_LIGHTS, default=int_list_to_str(options.get(CONF_LIGHTS, [])), + CONF_LIGHTS, default=list_to_str(options.get(CONF_LIGHTS, [])), ): str, vol.Optional( - CONF_EXCLUDE, default=int_list_to_str(options.get(CONF_EXCLUDE, [])), + CONF_EXCLUDE, default=list_to_str(options.get(CONF_EXCLUDE, [])), ): str, } diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index 52ba55b509c..5f4536decec 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -44,8 +44,8 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None: assert result["data"] == { CONF_CONTROLLER: "http://127.0.0.1:123", CONF_SOURCE: config_entries.SOURCE_USER, - CONF_LIGHTS: ["12", "13"], - CONF_EXCLUDE: ["14", "15"], + CONF_LIGHTS: [12, 13], + CONF_EXCLUDE: [14, 15], } assert result["result"].unique_id == controller.serial_number @@ -154,6 +154,6 @@ async def test_options(hass): ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"] == { - CONF_LIGHTS: ["1", "2", "3", "4", "5", "6", "7"], - CONF_EXCLUDE: ["8", "9", "10", "11", "12", "13", "14"], + CONF_LIGHTS: [1, 2, 3, 4, 5, 6, 7], + CONF_EXCLUDE: [8, 9, 10, 11, 12, 13, 14], } diff --git a/tests/components/vera/test_init.py b/tests/components/vera/test_init.py index f1e13a5f208..210037a2ca3 100644 --- a/tests/components/vera/test_init.py +++ b/tests/components/vera/test_init.py @@ -1,8 +1,14 @@ """Vera tests.""" +import pytest import pyvera as pv from requests.exceptions import RequestException -from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN +from homeassistant.components.vera import ( + CONF_CONTROLLER, + CONF_EXCLUDE, + CONF_LIGHTS, + DOMAIN, +) from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.core import HomeAssistant @@ -110,3 +116,71 @@ async def test_async_setup_entry_error( entry.add_to_hass(hass) assert not await hass.config_entries.async_setup(entry.entry_id) + + +@pytest.mark.parametrize( + ["options"], + [ + [{CONF_LIGHTS: [4, 10, 12, "AAA"], CONF_EXCLUDE: [1, "BBB"]}], + [{CONF_LIGHTS: ["4", "10", "12", "AAA"], CONF_EXCLUDE: ["1", "BBB"]}], + ], +) +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.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.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.device_id = 3 + vera_device3.name = "dev3" + vera_device3.category = pv.CATEGORY_SWITCH + vera_device3.is_switched_on = MagicMock(return_value=False) + entity_id3 = "switch.dev3_3" + + vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch + vera_device4.device_id = 4 + vera_device4.name = "dev4" + vera_device4.category = pv.CATEGORY_SWITCH + vera_device4.is_switched_on = MagicMock(return_value=False) + entity_id4 = "light.dev4_4" + + component_data = await vera_component_factory.configure_component( + hass=hass, + controller_config=new_simple_controller_config( + devices=(vera_device1, vera_device2, vera_device3, vera_device4), + config={**{CONF_CONTROLLER: "http://127.0.0.1:123"}, **options}, + ), + ) + + # Assert the entries were setup correctly. + config_entry = next(iter(hass.config_entries.async_entries(DOMAIN))) + assert config_entry.options == { + CONF_LIGHTS: [4, 10, 12], + CONF_EXCLUDE: [1], + } + + update_callback = component_data.controller_data.update_callback + + update_callback(vera_device1) + update_callback(vera_device2) + update_callback(vera_device3) + update_callback(vera_device4) + await hass.async_block_till_done() + + assert hass.states.get(entity_id1) is None + assert hass.states.get(entity_id2) is not None + assert hass.states.get(entity_id3) is not None + assert hass.states.get(entity_id4) is not None From 4de30ca2ce5c32295054ff36f648f87412e01884 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 2 May 2020 16:15:44 -0500 Subject: [PATCH 218/511] Improve stability of homekit media players (#35080) --- .../components/homekit/accessories.py | 7 +- .../components/homekit/type_media_players.py | 53 +++++++-------- homeassistant/components/homekit/util.py | 66 ++++++++++++++++++- .../homekit/test_type_media_players.py | 20 ++++++ tests/components/homekit/test_util.py | 14 ++++ 5 files changed, 125 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index ddafbd8fa66..e7724631717 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -164,13 +164,12 @@ def get_accessory(hass, driver, state, aid, config): elif state.domain == "media_player": device_class = state.attributes.get(ATTR_DEVICE_CLASS) - feature_list = config.get(CONF_FEATURE_LIST) + feature_list = config.get(CONF_FEATURE_LIST, []) if device_class == DEVICE_CLASS_TV: a_type = "TelevisionMediaPlayer" - else: - if feature_list and validate_media_player_features(state, feature_list): - a_type = "MediaPlayer" + elif validate_media_player_features(state, feature_list): + a_type = "MediaPlayer" elif state.domain == "sensor": device_class = state.attributes.get(ATTR_DEVICE_CLASS) diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 154355a0da3..209baad125e 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -64,6 +64,7 @@ from .const import ( SERV_TELEVISION, SERV_TELEVISION_SPEAKER, ) +from .util import get_media_player_features _LOGGER = logging.getLogger(__name__) @@ -83,10 +84,12 @@ MEDIA_PLAYER_KEYS = { # 15: "Information", } +# Names may not contain special characters +# or emjoi (/ is a special character for Apple) MODE_FRIENDLY_NAME = { FEATURE_ON_OFF: "Power", - FEATURE_PLAY_PAUSE: "Play/Pause", - FEATURE_PLAY_STOP: "Play/Stop", + FEATURE_PLAY_PAUSE: "Play-Pause", + FEATURE_PLAY_STOP: "Play-Stop", FEATURE_TOGGLE_MUTE: "Mute", } @@ -105,7 +108,9 @@ class MediaPlayer(HomeAccessory): FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None, } - feature_list = self.config[CONF_FEATURE_LIST] + feature_list = self.config.get( + CONF_FEATURE_LIST, get_media_player_features(state) + ) if FEATURE_ON_OFF in feature_list: name = self.generate_service_name(FEATURE_ON_OFF) @@ -213,7 +218,7 @@ class MediaPlayer(HomeAccessory): self.chars[FEATURE_PLAY_STOP].set_value(hk_state) if self.chars[FEATURE_TOGGLE_MUTE]: - current_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) + current_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED)) _LOGGER.debug( '%s: Set current state for "toggle_mute" to %s', self.entity_id, @@ -239,9 +244,7 @@ class TelevisionMediaPlayer(HomeAccessory): # Add additional characteristics if volume or input selection supported self.chars_tv = [] self.chars_speaker = [] - features = self.hass.states.get(self.entity_id).attributes.get( - ATTR_SUPPORTED_FEATURES, 0 - ) + features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if features & (SUPPORT_PLAY | SUPPORT_PAUSE): self.chars_tv.append(CHAR_REMOTE_KEY) @@ -252,7 +255,8 @@ class TelevisionMediaPlayer(HomeAccessory): if features & SUPPORT_VOLUME_SET: self.chars_speaker.append(CHAR_VOLUME) - if features & SUPPORT_SELECT_SOURCE: + 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) @@ -297,9 +301,7 @@ class TelevisionMediaPlayer(HomeAccessory): ) if self.support_select_source: - self.sources = self.hass.states.get(self.entity_id).attributes.get( - ATTR_INPUT_SOURCE_LIST, [] - ) + self.sources = source_list self.char_input_source = serv_tv.configure_char( CHAR_ACTIVE_IDENTIFIER, setter_callback=self.set_input_source ) @@ -379,14 +381,13 @@ class TelevisionMediaPlayer(HomeAccessory): hk_state = 0 if current_state not in ("None", STATE_OFF, STATE_UNKNOWN): hk_state = 1 - _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) # Set mute state if CHAR_VOLUME_SELECTOR in self.chars_speaker: - current_mute_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) + current_mute_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED)) _LOGGER.debug( "%s: Set current mute state to %s", self.entity_id, current_mute_state, ) @@ -394,20 +395,16 @@ class TelevisionMediaPlayer(HomeAccessory): self.char_mute.set_value(current_mute_state) # Set active input - if self.support_select_source: + if self.support_select_source and self.sources: source_name = new_state.attributes.get(ATTR_INPUT_SOURCE) - if self.sources: - _LOGGER.debug( - "%s: Set current input to %s", self.entity_id, source_name + _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) + else: + _LOGGER.warning( + "%s: Sources out of sync. Restart Home Assistant", self.entity_id, ) - 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) - else: - _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) + if self.char_input_source.value != 0: + self.char_input_source.set_value(0) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index 3ccf73d3925..5a8d1f98841 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -101,6 +101,40 @@ SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend( ) +HOMEKIT_CHAR_TRANSLATIONS = { + 0: " ", # nul + 10: " ", # nl + 13: " ", # cr + 33: "-", # ! + 34: " ", # " + 36: "-", # $ + 37: "-", # % + 40: "-", # ( + 41: "-", # ) + 42: "-", # * + 43: "-", # + + 47: "-", # / + 58: "-", # : + 59: "-", # ; + 60: "-", # < + 61: "-", # = + 62: "-", # > + 63: "-", # ? + 64: "-", # @ + 91: "-", # [ + 92: "-", # \ + 93: "-", # ] + 94: "-", # ^ + 95: " ", # _ + 96: "-", # ` + 123: "-", # { + 124: "-", # | + 125: "-", # } + 126: "-", # ~ + 127: "-", # del +} + + def validate_entity_config(values): """Validate config entry for CONF_ENTITY.""" if not isinstance(values, dict): @@ -138,8 +172,8 @@ def validate_entity_config(values): return entities -def validate_media_player_features(state, feature_list): - """Validate features for media players.""" +def get_media_player_features(state): + """Determine features for media players.""" features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported_modes = [] @@ -153,6 +187,20 @@ def validate_media_player_features(state, feature_list): supported_modes.append(FEATURE_PLAY_STOP) if features & media_player.const.SUPPORT_VOLUME_MUTE: supported_modes.append(FEATURE_TOGGLE_MUTE) + return supported_modes + + +def validate_media_player_features(state, feature_list): + """Validate features for media players.""" + supported_modes = get_media_player_features(state) + + if not supported_modes: + _LOGGER.error("%s does not support any media_player features", state.entity_id) + return False + + if not feature_list: + # Auto detected + return True error_list = [] for feature in feature_list: @@ -160,7 +208,9 @@ def validate_media_player_features(state, feature_list): error_list.append(feature) if error_list: - _LOGGER.error("%s does not support features: %s", state.entity_id, error_list) + _LOGGER.error( + "%s does not support media_player features: %s", state.entity_id, error_list + ) return False return True @@ -252,6 +302,16 @@ def convert_to_float(state): return None +def cleanup_name_for_homekit(name): + """Ensure the name of the device will not crash homekit.""" + # + # This is not a security measure. + # + # UNICODE_EMOJI is also not allowed but that + # likely isn't a problem + return name.translate(HOMEKIT_CHAR_TRANSLATIONS) + + def temperature_to_homekit(temperature, unit): """Convert temperature to Celsius for HomeKit.""" return round(temp_util.convert(temperature, unit, TEMP_CELSIUS), 1) diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index cb2de7264a8..9b8cf074d57 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -369,6 +369,26 @@ async def test_media_player_television_basic(hass, hk_driver, events, caplog): assert not caplog.messages or "Error" not in caplog.messages[-1] +async def test_media_player_television_supports_source_select_no_sources( + hass, hk_driver, events, caplog +): + """Test if basic tv that supports source select but is missing a source list.""" + entity_id = "media_player.television" + + # Supports turn_on', 'turn_off' + hass.states.async_set( + entity_id, + None, + {ATTR_DEVICE_CLASS: DEVICE_CLASS_TV, ATTR_SUPPORTED_FEATURES: 3469}, + ) + await hass.async_block_till_done() + acc = TelevisionMediaPlayer(hass, hk_driver, "MediaPlayer", entity_id, 2, None) + await acc.run_handler() + await hass.async_block_till_done() + + assert acc.support_select_source is False + + 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 diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index 2c8c93cee4c..d5ff923270b 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -23,6 +23,7 @@ from homeassistant.components.homekit.const import ( from homeassistant.components.homekit.util import ( HomeKitSpeedMapping, SpeedRange, + cleanup_name_for_homekit, convert_to_float, density_to_air_quality, dismiss_setup_message, @@ -177,6 +178,19 @@ def test_convert_to_float(): assert convert_to_float(None) is None +def test_cleanup_name_for_homekit(): + """Ensure name sanitize works as expected.""" + + assert cleanup_name_for_homekit("abc") == "abc" + assert cleanup_name_for_homekit("a b c") == "a b c" + assert cleanup_name_for_homekit("ab_c") == "ab c" + assert ( + cleanup_name_for_homekit('ab!@#$%^&*()-=":.,> Date: Sat, 2 May 2020 23:16:18 +0200 Subject: [PATCH 219/511] UniFi - Add support for 2.4/5 GHz separated SSIDs (#35062) --- homeassistant/components/unifi/config_flow.py | 7 ++++++- homeassistant/components/unifi/controller.py | 2 +- homeassistant/components/unifi/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/unifi/test_config_flow.py | 9 ++++++--- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 72ba593bae6..ad4f3ebf1a1 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -219,7 +219,12 @@ class UnifiOptionsFlowHandler(config_entries.OptionsFlow): self.options.update(user_input) return await self.async_step_client_control() - ssid_filter = {wlan: wlan for wlan in self.controller.api.wlans} + ssids = list(self.controller.api.wlans) + [ + f"{wlan.name}{wlan.name_combine_suffix}" + for wlan in self.controller.api.wlans.values() + if not wlan.name_combine_enabled + ] + ssid_filter = {ssid: ssid for ssid in sorted(ssids)} return self.async_show_form( step_id="device_tracker", diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 8ccb42794ec..841906d02fd 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -190,7 +190,7 @@ class UniFiController: elif signal == SIGNAL_DATA and data: if DATA_EVENT in data: - if data[DATA_EVENT].event in ( + if next(iter(data[DATA_EVENT])).event in ( WIRELESS_CLIENT_CONNECTED, WIRELESS_GUEST_CONNECTED, ): diff --git a/homeassistant/components/unifi/manifest.json b/homeassistant/components/unifi/manifest.json index 0a5ba84cdb3..8c05d195316 100644 --- a/homeassistant/components/unifi/manifest.json +++ b/homeassistant/components/unifi/manifest.json @@ -3,7 +3,7 @@ "name": "Ubiquiti UniFi", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/unifi", - "requirements": ["aiounifi==18"], + "requirements": ["aiounifi==20"], "codeowners": ["@kane610"], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 5882864e328..5623c120c15 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -215,7 +215,7 @@ aiopylgtv==0.3.3 aioswitcher==1.1.0 # homeassistant.components.unifi -aiounifi==18 +aiounifi==20 # homeassistant.components.wwlln aiowwlln==2.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9c5193ff14a..89eacf76e70 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -101,7 +101,7 @@ aiopylgtv==0.3.3 aioswitcher==1.1.0 # homeassistant.components.unifi -aiounifi==18 +aiounifi==20 # homeassistant.components.wwlln aiowwlln==2.0.2 diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 489ae25a60c..1ca82fb12f1 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -31,7 +31,10 @@ from tests.common import MockConfigEntry CLIENTS = [{"mac": "00:00:00:00:00:01"}] -WLANS = [{"name": "SSID 1"}, {"name": "SSID 2"}] +WLANS = [ + {"name": "SSID 1"}, + {"name": "SSID 2", "name_combine_enabled": False, "name_combine_suffix": "_IOT"}, +] async def test_flow_works(hass, aioclient_mock, mock_discovery): @@ -283,7 +286,7 @@ async def test_advanced_option_flow(hass): CONF_TRACK_CLIENTS: False, CONF_TRACK_WIRED_CLIENTS: False, CONF_TRACK_DEVICES: False, - CONF_SSID_FILTER: ["SSID 1"], + CONF_SSID_FILTER: ["SSID 1", "SSID 2_IOT"], CONF_DETECTION_TIME: 100, }, ) @@ -308,7 +311,7 @@ async def test_advanced_option_flow(hass): CONF_TRACK_CLIENTS: False, CONF_TRACK_WIRED_CLIENTS: False, CONF_TRACK_DEVICES: False, - CONF_SSID_FILTER: ["SSID 1"], + CONF_SSID_FILTER: ["SSID 1", "SSID 2_IOT"], CONF_DETECTION_TIME: 100, CONF_IGNORE_WIRED_BUG: False, CONF_POE_CLIENTS: False, From a2048b4c7a8ce51c4eff2800d0524d8aa981da13 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sat, 2 May 2020 23:16:45 +0200 Subject: [PATCH 220/511] UniFi - Catch controllers running on UniFi OS that don't have a local user configured (#35060) --- homeassistant/components/unifi/config_flow.py | 12 ++++- homeassistant/components/unifi/errors.py | 4 ++ homeassistant/components/unifi/strings.json | 1 + .../components/unifi/translations/en.json | 1 + tests/components/unifi/test_config_flow.py | 47 +++++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index ad4f3ebf1a1..8c836f77131 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -32,7 +32,12 @@ from .const import ( LOGGER, ) from .controller import get_controller -from .errors import AlreadyConfigured, AuthenticationRequired, CannotConnect +from .errors import ( + AlreadyConfigured, + AuthenticationRequired, + CannotConnect, + NoLocalUser, +) DEFAULT_PORT = 8443 DEFAULT_SITE_ID = "default" @@ -129,6 +134,8 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): for site in self.sites.values(): if desc == site["desc"]: + if "role" not in site: + raise NoLocalUser self.config[CONF_SITE_ID] = site["name"] break @@ -147,6 +154,9 @@ class UnifiFlowHandler(config_entries.ConfigFlow, domain=UNIFI_DOMAIN): except AlreadyConfigured: return self.async_abort(reason="already_configured") + except NoLocalUser: + return self.async_abort(reason="no_local_user") + if len(self.sites) == 1: self.desc = next(iter(self.sites.values()))["desc"] return await self.async_step_site(user_input={}) diff --git a/homeassistant/components/unifi/errors.py b/homeassistant/components/unifi/errors.py index c90c4956312..e0da64f245c 100644 --- a/homeassistant/components/unifi/errors.py +++ b/homeassistant/components/unifi/errors.py @@ -22,5 +22,9 @@ class LoginRequired(UnifiException): """Component got logged out.""" +class NoLocalUser(UnifiException): + """No local user.""" + + class UserLevel(UnifiException): """User level too low.""" diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index da1d6200ed5..2650066da5f 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -20,6 +20,7 @@ }, "abort": { "already_configured": "Controller site is already configured", + "no_local_user": "No local user found, configure a local account on controller and try again", "user_privilege": "User needs to be administrator" } }, diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index e49bbbcc50e..ad14f09b300 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Controller site is already configured", + "no_local_user": "No local user found, configure a local account on controller and try again", "user_privilege": "User needs to be administrator" }, "error": { diff --git a/tests/components/unifi/test_config_flow.py b/tests/components/unifi/test_config_flow.py index 1ca82fb12f1..16777e5d9a9 100644 --- a/tests/components/unifi/test_config_flow.py +++ b/tests/components/unifi/test_config_flow.py @@ -185,6 +185,53 @@ async def test_flow_fails_site_already_configured(hass, aioclient_mock): ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_flow_fails_site_has_no_local_user(hass, aioclient_mock): + """Test config flow.""" + entry = MockConfigEntry( + domain=UNIFI_DOMAIN, data={"controller": {"host": "1.2.3.4", "site": "site_id"}} + ) + entry.add_to_hass(hass) + + 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" + + aioclient_mock.get("https://1.2.3.4:1234", status=302) + + aioclient_mock.post( + "https://1.2.3.4:1234/api/login", + json={"data": "login successful", "meta": {"rc": "ok"}}, + headers={"content-type": "application/json"}, + ) + + aioclient_mock.get( + "https://1.2.3.4:1234/api/self/sites", + json={ + "data": [{"desc": "Site name", "name": "site_id"}], + "meta": {"rc": "ok"}, + }, + headers={"content-type": "application/json"}, + ) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 1234, + CONF_VERIFY_SSL: True, + }, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_local_user" async def test_flow_fails_user_credentials_faulty(hass, aioclient_mock): From 5450cda3a548ef18ec815666e45f788d44a37802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 3 May 2020 00:35:55 +0300 Subject: [PATCH 221/511] Async vs sync inheritance mismatch fixes (#35088) --- homeassistant/components/atag/water_heater.py | 2 +- homeassistant/components/fortigate/device_tracker.py | 2 +- homeassistant/components/hunterdouglas_powerview/cover.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index bb1f72d6a8e..2b2093c3c7e 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -49,7 +49,7 @@ class AtagWaterHeater(AtagEntity, WaterHeaterDevice): """List of available operation modes.""" return OPERATION_LIST - async def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" if await self.coordinator.atag.dhw_set_temp(kwargs.get(ATTR_TEMPERATURE)): self.async_write_ha_state() diff --git a/homeassistant/components/fortigate/device_tracker.py b/homeassistant/components/fortigate/device_tracker.py index b51dc6843aa..23df0ee266e 100644 --- a/homeassistant/components/fortigate/device_tracker.py +++ b/homeassistant/components/fortigate/device_tracker.py @@ -60,7 +60,7 @@ class FortigateDeviceScanner(DeviceScanner): await self.async_update_info() return [device.mac for device in self.last_results] - async def get_device_name(self, device): + def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" name = next( (result.hostname for result in self.last_results if result.mac == device), diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 8364b3273ca..e14142677e3 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -161,7 +161,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): self._async_update_from_command(await self._shade.stop()) await self._async_force_refresh_state() - async def set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs): """Move the shade to a specific position.""" if ATTR_POSITION not in kwargs: return From 43d63d0fe591d14623e923cbfaf8f0148468493b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 3 May 2020 00:52:29 +0300 Subject: [PATCH 222/511] Use savoury1/ffmpeg4 PPA on Travis, PyAV 7 needs FFmpeg >= 4 (#35090) --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6add8c15bfc..a01398651da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,9 @@ addons: - libswscale-dev - libswresample-dev - libavfilter-dev + sources: + - sourceline: ppa:savoury1/ffmpeg4 + matrix: fast_finish: true include: From 752679c55d54c17683940a482fafb98a130c0556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Sun, 3 May 2020 00:57:48 +0300 Subject: [PATCH 223/511] Check isinstance on collections.abc, not typing classes (#35087) --- homeassistant/components/smartthings/climate.py | 3 ++- homeassistant/helpers/template.py | 3 ++- homeassistant/scripts/check_config.py | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/smartthings/climate.py b/homeassistant/components/smartthings/climate.py index e9c0e749ca8..6ce872cdac7 100644 --- a/homeassistant/components/smartthings/climate.py +++ b/homeassistant/components/smartthings/climate.py @@ -1,7 +1,8 @@ """Support for climate devices through the SmartThings cloud API.""" import asyncio +from collections.abc import Iterable import logging -from typing import Iterable, Optional, Sequence +from typing import Optional, Sequence from pysmartthings import Attribute, Capability diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 3f9924d00d5..bc868fa16b8 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,5 +1,6 @@ """Template helper methods for rendering strings with Home Assistant data.""" import base64 +import collections.abc from datetime import datetime from functools import wraps import json @@ -503,7 +504,7 @@ def expand(hass: HomeAssistantType, *args: Any) -> Iterable[State]: continue elif isinstance(entity, State): entity_id = entity.entity_id - elif isinstance(entity, Iterable): + elif isinstance(entity, collections.abc.Iterable): search += entity continue else: diff --git a/homeassistant/scripts/check_config.py b/homeassistant/scripts/check_config.py index 627f5b9d976..8b4c6a446f2 100644 --- a/homeassistant/scripts/check_config.py +++ b/homeassistant/scripts/check_config.py @@ -1,10 +1,11 @@ """Script to check the configuration file.""" import argparse from collections import OrderedDict +from collections.abc import Mapping, Sequence from glob import glob import logging import os -from typing import Any, Callable, Dict, List, Sequence, Tuple +from typing import Any, Callable, Dict, List, Tuple from unittest.mock import patch from homeassistant import bootstrap, core @@ -252,7 +253,7 @@ def dump_dict(layer, indent_count=3, listi=False, **kwargs): indent_str = indent_count * " " if listi or isinstance(layer, list): indent_str = indent_str[:-1] + "-" - if isinstance(layer, Dict): + if isinstance(layer, Mapping): for key, value in sorted(layer.items(), key=sort_dict_key): if isinstance(value, (dict, list)): print(indent_str, str(key) + ":", line_info(value, **kwargs)) From 78f846d532ad884a993182fe3cd12eac4e7f4428 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 3 May 2020 00:02:26 +0000 Subject: [PATCH 224/511] [ci skip] Translation update --- .../adguard/translations/es-419.json | 1 + .../airvisual/translations/es-419.json | 7 +- .../components/airvisual/translations/fr.json | 20 +++- .../components/atag/translations/es-419.json | 1 + .../components/atag/translations/fr.json | 9 +- .../components/auth/translations/no.json | 2 +- .../components/axis/translations/es-419.json | 2 + .../binary_sensor/translations/es-419.json | 4 + .../braviatv/translations/es-419.json | 7 ++ .../components/braviatv/translations/no.json | 2 +- .../brother/translations/es-419.json | 4 +- .../components/brother/translations/no.json | 2 +- .../cert_expiry/translations/es-419.json | 2 + .../climate/translations/es-419.json | 11 +- .../coolmaster/translations/es-419.json | 5 + .../coronavirus/translations/es-419.json | 15 +++ .../components/cover/translations/es-419.json | 26 +++++ .../deconz/translations/es-419.json | 60 +++++++++- .../components/deconz/translations/es.json | 8 +- .../components/deconz/translations/no.json | 2 +- .../components/demo/translations/es-419.json | 17 +++ .../directv/translations/es-419.json | 24 ++++ .../doorbird/translations/es-419.json | 8 +- .../ecobee/translations/es-419.json | 7 +- .../elgato/translations/es-419.json | 12 +- .../components/elgato/translations/no.json | 2 +- .../components/elkm1/translations/es-419.json | 27 +++++ .../emulated_roku/translations/es-419.json | 4 +- .../components/esphome/translations/de.json | 3 +- .../esphome/translations/es-419.json | 3 +- .../components/esphome/translations/it.json | 3 +- .../components/esphome/translations/no.json | 2 +- .../components/fan/translations/es-419.json | 4 + .../components/flume/translations/es-419.json | 24 ++++ .../flunearyou/translations/es-419.json | 20 ++++ .../freebox/translations/es-419.json | 25 ++++ .../fritzbox/translations/es-419.json | 33 ++++++ .../components/fritzbox/translations/fr.json | 3 + .../components/fritzbox/translations/no.json | 2 +- .../garmin_connect/translations/es-419.json | 3 +- .../components/gdacs/translations/es-419.json | 15 +++ .../components/geofency/translations/no.json | 2 +- .../geonetnz_quakes/translations/es-419.json | 16 +++ .../geonetnz_volcano/translations/es-419.json | 15 +++ .../components/gios/translations/es-419.json | 15 ++- .../glances/translations/es-419.json | 8 +- .../griddy/translations/es-419.json | 20 ++++ .../hangouts/translations/es-419.json | 1 + .../harmony/translations/es-419.json | 36 ++++++ .../components/harmony/translations/no.json | 2 +- .../components/heos/translations/es-419.json | 4 + .../hisense_aehw4a1/translations/es-419.json | 14 +++ .../hisense_aehw4a1/translations/no.json | 2 +- .../components/homekit/translations/de.json | 29 +++++ .../components/homekit/translations/en.json | 70 ++++++------ .../homekit/translations/es-419.json | 53 +++++++++ .../components/homekit/translations/es.json | 18 +++ .../components/homekit/translations/it.json | 53 +++++++++ .../homekit/translations/zh-Hans.json | 49 ++++++++ .../translations/es-419.json | 11 +- .../translations/zh-Hans.json | 1 + .../translations/es-419.json | 6 +- .../homematicip_cloud/translations/es.json | 2 +- .../huawei_lte/translations/es-419.json | 42 +++++++ .../components/hue/translations/es-419.json | 29 ++++- .../components/hue/translations/es.json | 18 +-- .../translations/es-419.json | 24 ++++ .../iaqualink/translations/es-419.json | 9 +- .../icloud/translations/es-419.json | 38 ++++++ .../components/ifttt/translations/es-419.json | 3 +- .../components/ios/translations/no.json | 2 +- .../components/ipma/translations/es-419.json | 1 + .../components/ipp/translations/es-419.json | 34 ++++++ .../components/ipp/translations/no.json | 2 +- .../components/iqvia/translations/es-419.json | 5 +- .../translations/es-419.json | 23 ++++ .../components/izone/translations/es-419.json | 14 +++ .../components/izone/translations/no.json | 2 +- .../konnected/translations/es-419.json | 108 ++++++++++++++++++ .../components/konnected/translations/fr.json | 14 ++- .../life360/translations/es-419.json | 5 + .../components/light/translations/es-419.json | 8 ++ .../components/light/translations/no.json | 2 +- .../components/linky/translations/es-419.json | 3 + .../local_ip/translations/es-419.json | 16 +++ .../components/lock/translations/es-419.json | 15 +++ .../logi_circle/translations/es-419.json | 9 +- .../lutron_caseta/translations/es-419.json | 3 + .../media_player/translations/es-419.json | 9 ++ .../melcloud/translations/es-419.json | 22 ++++ .../components/melcloud/translations/fr.json | 3 + .../meteo_france/translations/es-419.json | 17 +++ .../mikrotik/translations/es-419.json | 36 ++++++ .../components/mikrotik/translations/no.json | 2 +- .../minecraft_server/translations/es-419.json | 22 ++++ .../minecraft_server/translations/fr.json | 6 + .../minecraft_server/translations/no.json | 2 +- .../mobile_app/translations/es-419.json | 1 + .../mobile_app/translations/no.json | 2 +- .../monoprice/translations/es-419.json | 40 +++++++ .../moon/translations/sensor.es-419.json | 7 +- .../components/mqtt/translations/es-419.json | 22 ++++ .../components/myq/translations/es-419.json | 21 ++++ .../components/neato/translations/es-419.json | 26 +++++ .../components/nest/translations/es-419.json | 3 + .../netatmo/translations/es-419.json | 17 +++ .../components/nexia/translations/es-419.json | 21 ++++ .../notion/translations/es-419.json | 3 + .../nuheat/translations/es-419.json | 24 ++++ .../components/nut/translations/es-419.json | 46 ++++++++ .../components/nws/translations/es-419.json | 23 ++++ .../components/onvif/translations/de.json | 58 ++++++++++ .../components/onvif/translations/es-419.json | 58 ++++++++++ .../components/onvif/translations/fr.json | 2 +- .../components/onvif/translations/it.json | 58 ++++++++++ .../opentherm_gw/translations/es-419.json | 31 +++++ .../panasonic_viera/translations/es-419.json | 31 +++++ .../components/plex/translations/es-419.json | 10 ++ .../components/point/translations/es-419.json | 10 +- .../powerwall/translations/es-419.json | 20 ++++ .../components/powerwall/translations/fr.json | 3 +- .../components/ps4/translations/es-419.json | 4 + .../translations/es-419.json | 17 +++ .../rachio/translations/es-419.json | 30 +++++ .../rainmachine/translations/es-419.json | 3 + .../components/ring/translations/es-419.json | 26 +++++ .../components/roku/translations/es-419.json | 25 ++++ .../roomba/translations/es-419.json | 32 ++++++ .../samsungtv/translations/es-419.json | 26 +++++ .../components/samsungtv/translations/no.json | 2 +- .../season/translations/sensor.es-419.json | 9 +- .../components/sense/translations/es-419.json | 21 ++++ .../sensor/translations/es-419.json | 11 ++ .../sentry/translations/es-419.json | 17 +++ .../shopping_list/translations/es-419.json | 14 +++ .../simplisafe/translations/es-419.json | 14 +++ .../simplisafe/translations/fr.json | 1 + .../smartthings/translations/es-419.json | 23 ++++ .../solaredge/translations/es-419.json | 20 ++++ .../solarlog/translations/es-419.json | 20 ++++ .../components/soma/translations/es-419.json | 24 ++++ .../components/somfy/translations/es-419.json | 16 ++- .../spotify/translations/es-419.json | 17 +++ .../starline/translations/es-419.json | 41 +++++++ .../switch/translations/es-419.json | 1 + .../synology_dsm/translations/es-419.json | 45 ++++++++ .../synology_dsm/translations/no.json | 2 +- .../components/tado/translations/es-419.json | 33 ++++++ .../tellduslive/translations/es-419.json | 4 +- .../components/tesla/translations/es-419.json | 30 +++++ .../components/toon/translations/es-419.json | 5 + .../totalconnect/translations/es-419.json | 19 +++ .../traccar/translations/es-419.json | 9 ++ .../components/tradfri/translations/es.json | 4 +- .../transmission/translations/es-419.json | 25 +++- .../twentemilieu/translations/es-419.json | 9 ++ .../twentemilieu/translations/no.json | 2 +- .../components/unifi/translations/de.json | 3 + .../components/unifi/translations/es-419.json | 43 ++++++- .../components/unifi/translations/it.json | 8 ++ .../components/upnp/translations/no.json | 2 +- .../vacuum/translations/es-419.json | 14 +++ .../components/vera/translations/es-419.json | 31 +++++ .../components/vilfo/translations/es-419.json | 13 +++ .../components/vilfo/translations/no.json | 2 +- .../components/vizio/translations/es-419.json | 32 +++++- .../components/vizio/translations/no.json | 2 +- .../withings/translations/es-419.json | 16 +++ .../components/wled/translations/es-419.json | 3 + .../components/wled/translations/no.json | 4 +- .../components/wwlln/translations/es-419.json | 3 + .../xiaomi_miio/translations/es-419.json | 29 +++++ .../xiaomi_miio/translations/fr.json | 29 +++++ .../xiaomi_miio/translations/zh-Hans.json | 29 +++++ .../components/zha/translations/es-419.json | 39 ++++++- 175 files changed, 2725 insertions(+), 121 deletions(-) create mode 100644 homeassistant/components/coronavirus/translations/es-419.json create mode 100644 homeassistant/components/directv/translations/es-419.json create mode 100644 homeassistant/components/elkm1/translations/es-419.json create mode 100644 homeassistant/components/flume/translations/es-419.json create mode 100644 homeassistant/components/flunearyou/translations/es-419.json create mode 100644 homeassistant/components/freebox/translations/es-419.json create mode 100644 homeassistant/components/fritzbox/translations/es-419.json create mode 100644 homeassistant/components/gdacs/translations/es-419.json create mode 100644 homeassistant/components/geonetnz_quakes/translations/es-419.json create mode 100644 homeassistant/components/geonetnz_volcano/translations/es-419.json create mode 100644 homeassistant/components/griddy/translations/es-419.json create mode 100644 homeassistant/components/harmony/translations/es-419.json create mode 100644 homeassistant/components/hisense_aehw4a1/translations/es-419.json create mode 100644 homeassistant/components/homekit/translations/de.json create mode 100644 homeassistant/components/homekit/translations/es-419.json create mode 100644 homeassistant/components/homekit/translations/es.json create mode 100644 homeassistant/components/homekit/translations/it.json create mode 100644 homeassistant/components/homekit/translations/zh-Hans.json create mode 100644 homeassistant/components/huawei_lte/translations/es-419.json create mode 100644 homeassistant/components/hunterdouglas_powerview/translations/es-419.json create mode 100644 homeassistant/components/icloud/translations/es-419.json create mode 100644 homeassistant/components/ipp/translations/es-419.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/es-419.json create mode 100644 homeassistant/components/izone/translations/es-419.json create mode 100644 homeassistant/components/konnected/translations/es-419.json create mode 100644 homeassistant/components/local_ip/translations/es-419.json create mode 100644 homeassistant/components/lutron_caseta/translations/es-419.json create mode 100644 homeassistant/components/melcloud/translations/es-419.json create mode 100644 homeassistant/components/meteo_france/translations/es-419.json create mode 100644 homeassistant/components/mikrotik/translations/es-419.json create mode 100644 homeassistant/components/minecraft_server/translations/es-419.json create mode 100644 homeassistant/components/monoprice/translations/es-419.json create mode 100644 homeassistant/components/myq/translations/es-419.json create mode 100644 homeassistant/components/neato/translations/es-419.json create mode 100644 homeassistant/components/netatmo/translations/es-419.json create mode 100644 homeassistant/components/nexia/translations/es-419.json create mode 100644 homeassistant/components/nuheat/translations/es-419.json create mode 100644 homeassistant/components/nut/translations/es-419.json create mode 100644 homeassistant/components/nws/translations/es-419.json create mode 100644 homeassistant/components/onvif/translations/de.json create mode 100644 homeassistant/components/onvif/translations/es-419.json create mode 100644 homeassistant/components/onvif/translations/it.json create mode 100644 homeassistant/components/opentherm_gw/translations/es-419.json create mode 100644 homeassistant/components/panasonic_viera/translations/es-419.json create mode 100644 homeassistant/components/powerwall/translations/es-419.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/es-419.json create mode 100644 homeassistant/components/rachio/translations/es-419.json create mode 100644 homeassistant/components/ring/translations/es-419.json create mode 100644 homeassistant/components/roku/translations/es-419.json create mode 100644 homeassistant/components/roomba/translations/es-419.json create mode 100644 homeassistant/components/samsungtv/translations/es-419.json create mode 100644 homeassistant/components/sense/translations/es-419.json create mode 100644 homeassistant/components/sentry/translations/es-419.json create mode 100644 homeassistant/components/shopping_list/translations/es-419.json create mode 100644 homeassistant/components/solaredge/translations/es-419.json create mode 100644 homeassistant/components/solarlog/translations/es-419.json create mode 100644 homeassistant/components/soma/translations/es-419.json create mode 100644 homeassistant/components/spotify/translations/es-419.json create mode 100644 homeassistant/components/starline/translations/es-419.json create mode 100644 homeassistant/components/synology_dsm/translations/es-419.json create mode 100644 homeassistant/components/tado/translations/es-419.json create mode 100644 homeassistant/components/tesla/translations/es-419.json create mode 100644 homeassistant/components/totalconnect/translations/es-419.json create mode 100644 homeassistant/components/vera/translations/es-419.json create mode 100644 homeassistant/components/xiaomi_miio/translations/es-419.json create mode 100644 homeassistant/components/xiaomi_miio/translations/fr.json create mode 100644 homeassistant/components/xiaomi_miio/translations/zh-Hans.json diff --git a/homeassistant/components/adguard/translations/es-419.json b/homeassistant/components/adguard/translations/es-419.json index 5a36b35d028..f2ce862b083 100644 --- a/homeassistant/components/adguard/translations/es-419.json +++ b/homeassistant/components/adguard/translations/es-419.json @@ -16,6 +16,7 @@ }, "user": { "data": { + "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto", "ssl": "AdGuard Home utiliza un certificado SSL", diff --git a/homeassistant/components/airvisual/translations/es-419.json b/homeassistant/components/airvisual/translations/es-419.json index aea8c1ad574..7b3116fe636 100644 --- a/homeassistant/components/airvisual/translations/es-419.json +++ b/homeassistant/components/airvisual/translations/es-419.json @@ -4,7 +4,9 @@ "already_configured": "Estas coordenadas ya han sido registradas." }, "error": { - "invalid_api_key": "Clave de API inv\u00e1lida" + "general_error": "Se ha producido un error desconocido.", + "invalid_api_key": "Clave de API inv\u00e1lida", + "unable_to_connect": "No se puede conectar a la unidad Node/Pro." }, "step": { "geography": { @@ -13,12 +15,15 @@ "latitude": "Latitud", "longitude": "Longitud" }, + "description": "Use la API de AirVisual para monitorear una ubicaci\u00f3n geogr\u00e1fica.", "title": "Configurar una geograf\u00eda" }, "node_pro": { "data": { + "ip_address": "Direcci\u00f3n IP/nombre de host de la unidad", "password": "Contrase\u00f1a de la unidad" }, + "description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.", "title": "Configurar un AirVisual Node/Pro" }, "user": { diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index d2013b0f17d..1d0f35a437f 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -4,7 +4,9 @@ "already_configured": "Cette cl\u00e9 API est d\u00e9j\u00e0 utilis\u00e9e." }, "error": { - "invalid_api_key": "Cl\u00e9 API invalide" + "general_error": "Une erreur inconnue est survenue.", + "invalid_api_key": "Cl\u00e9 API invalide", + "unable_to_connect": "Impossible de se connecter \u00e0 l'unit\u00e9 Node / Pro." }, "step": { "geography": { @@ -12,13 +14,25 @@ "api_key": "Cl\u00e9 d'API", "latitude": "Latitude", "longitude": "Longitude" - } + }, + "title": "Configurer une g\u00e9ographie" + }, + "node_pro": { + "data": { + "ip_address": "Adresse IP / nom d'h\u00f4te de l'unit\u00e9", + "password": "Mot de passe de l'unit\u00e9" + }, + "description": "Surveillez une unit\u00e9 AirVisual personnelle. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.", + "title": "Configurer un AirVisual Node/Pro" }, "user": { "data": { "api_key": "Cl\u00e9 API", + "cloud_api": "Localisation g\u00e9ographique", "latitude": "Latitude", - "longitude": "Longitude" + "longitude": "Longitude", + "node_pro": "AirVisual Node Pro", + "type": "Type d'int\u00e9gration" }, "description": "Surveiller la qualit\u00e9 de l\u2019air dans un emplacement g\u00e9ographique.", "title": "Configurer AirVisual" diff --git a/homeassistant/components/atag/translations/es-419.json b/homeassistant/components/atag/translations/es-419.json index a833218e311..214dd0e9004 100644 --- a/homeassistant/components/atag/translations/es-419.json +++ b/homeassistant/components/atag/translations/es-419.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "host": "Host", "port": "Puerto (10000)" }, "title": "Conectarse al dispositivo" diff --git a/homeassistant/components/atag/translations/fr.json b/homeassistant/components/atag/translations/fr.json index ace565408f6..f7cf39f001c 100644 --- a/homeassistant/components/atag/translations/fr.json +++ b/homeassistant/components/atag/translations/fr.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "Un seul appareil Atag peut \u00eatre ajout\u00e9 \u00e0 Home Assistant" + }, + "error": { + "connection_error": "Impossible de se connecter, veuillez r\u00e9essayer" + }, "step": { "user": { "data": { @@ -9,5 +15,6 @@ "title": "Se connecter \u00e0 l'appareil" } } - } + }, + "title": "Atag" } \ No newline at end of file diff --git a/homeassistant/components/auth/translations/no.json b/homeassistant/components/auth/translations/no.json index 48b5db8a3b6..1758219c56a 100644 --- a/homeassistant/components/auth/translations/no.json +++ b/homeassistant/components/auth/translations/no.json @@ -26,7 +26,7 @@ "step": { "init": { "description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte engangspassord, skann QR-koden med autentiseringsappen din. Hvis du ikke har en, kan vi anbefale enten [Google Authenticator](https://support.google.com/accounts/answer/1066447) eller [Authy](https://authy.com/). \n\n {qr_code} \n \nEtter at du har skannet koden, skriver du inn den seks-sifrede koden fra appen din for \u00e5 kontrollere oppsettet. Dersom du har problemer med \u00e5 skanne QR-koden kan du taste inn f\u00f8lgende kode manuelt: **`{code}`**.", - "title": "Konfigurer tofaktorautentisering ved hjelp av TOTP" + "title": "Sett opp tofaktorautentisering ved hjelp av TOTP" } }, "title": "TOTP" diff --git a/homeassistant/components/axis/translations/es-419.json b/homeassistant/components/axis/translations/es-419.json index a86131723e3..b5d1cb4ca7b 100644 --- a/homeassistant/components/axis/translations/es-419.json +++ b/homeassistant/components/axis/translations/es-419.json @@ -12,9 +12,11 @@ "device_unavailable": "El dispositivo no est\u00e1 disponible", "faulty_credentials": "Credenciales de usuario incorrectas" }, + "flow_title": "Dispositivo Axis: {name} ({host})", "step": { "user": { "data": { + "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto", "username": "Nombre de usuario" diff --git a/homeassistant/components/binary_sensor/translations/es-419.json b/homeassistant/components/binary_sensor/translations/es-419.json index 5bada49741e..d8cc4219097 100644 --- a/homeassistant/components/binary_sensor/translations/es-419.json +++ b/homeassistant/components/binary_sensor/translations/es-419.json @@ -28,6 +28,7 @@ "is_not_occupied": "{entity_name} no est\u00e1 ocupado", "is_not_open": "{entity_name} est\u00e1 cerrado", "is_not_plugged_in": "{entity_name} est\u00e1 desconectado", + "is_not_powered": "{entity_name} no tiene encendido", "is_not_present": "{entity_name} no est\u00e1 presente", "is_not_unsafe": "{entity_name} es seguro", "is_occupied": "{entity_name} est\u00e1 ocupado", @@ -68,13 +69,16 @@ "not_locked": "{entity_name} desbloqueado", "not_moist": "{entity_name} se sec\u00f3", "not_moving": "{entity_name} dej\u00f3 de moverse", + "not_occupied": "{entity_name} se desocup\u00f3", "not_opened": "{entity_name} cerrado", "not_plugged_in": "{entity_name} desconectado", + "not_powered": "{entity_name} no encendido", "not_present": "{entity_name} no presente", "not_unsafe": "{entity_name} se volvi\u00f3 seguro", "occupied": "{entity_name} se ocup\u00f3", "opened": "{entity_name} abierto", "plugged_in": "{entity_name} enchufado", + "powered": "{entity_name} encendido", "present": "{entity_name} presente", "problem": "{entity_name} comenz\u00f3 a detectar problemas", "smoke": "{entity_name} comenz\u00f3 a detectar humo", diff --git a/homeassistant/components/braviatv/translations/es-419.json b/homeassistant/components/braviatv/translations/es-419.json index 48457826a52..820ea329a0c 100644 --- a/homeassistant/components/braviatv/translations/es-419.json +++ b/homeassistant/components/braviatv/translations/es-419.json @@ -4,6 +4,8 @@ "already_configured": "Esta televisi\u00f3n ya est\u00e1 configurada." }, "error": { + "cannot_connect": "No se pudo conectar, host inv\u00e1lido o c\u00f3digo PIN.", + "invalid_host": "Nombre de host o direcci\u00f3n IP no v\u00e1lidos.", "unsupported_model": "Su modelo de televisi\u00f3n no es compatible." }, "step": { @@ -11,9 +13,14 @@ "data": { "pin": "C\u00f3digo PIN" }, + "description": "Ingrese el c\u00f3digo PIN que se muestra en la televisi\u00f3n Sony Bravia. \n\nSi no se muestra el c\u00f3digo PIN, debe cancelar el registro de Home Assistant en su televisi\u00f3n, vaya a: Configuraci\u00f3n -> Red -> Configuraci\u00f3n del dispositivo remoto - > Cancelar registro del dispositivo remoto.", "title": "Autorizar Sony Bravia TV" }, "user": { + "data": { + "host": "Nombre de host de TV o direcci\u00f3n IP" + }, + "description": "Configure la integraci\u00f3n de Sony Bravia TV. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/braviatv \n\n Aseg\u00farese de que su televisi\u00f3n est\u00e9 encendida.", "title": "Sony Bravia TV" } } diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index 45644446a3b..f9d034d48fb 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -20,7 +20,7 @@ "data": { "host": "TV-vertsnavn eller IP-adresse" }, - "description": "Konfigurer Sony Bravia TV-integrasjon. Hvis du har problemer med konfigurasjonen, g\u00e5 til: https://www.home-assistant.io/integrations/braviatv \n\n Forsikre deg om at TV-en er sl\u00e5tt p\u00e5.", + "description": "Sett opp Sony Bravia TV-integrasjon. Hvis du har problemer med konfigurasjonen, g\u00e5 til: https://www.home-assistant.io/integrations/braviatv \n\n Forsikre deg om at TV-en er sl\u00e5tt p\u00e5.", "title": "" } } diff --git a/homeassistant/components/brother/translations/es-419.json b/homeassistant/components/brother/translations/es-419.json index 0cb35449bc5..286851ba454 100644 --- a/homeassistant/components/brother/translations/es-419.json +++ b/homeassistant/components/brother/translations/es-419.json @@ -6,12 +6,14 @@ }, "error": { "connection_error": "Error de conexi\u00f3n.", - "snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible." + "snmp_error": "El servidor SNMP est\u00e1 apagado o la impresora no es compatible.", + "wrong_host": "Nombre de host o direcci\u00f3n IP no v\u00e1lidos." }, "flow_title": "Impresora Brother: {model} {serial_number}", "step": { "user": { "data": { + "host": "Nombre de host de la impresora o direcci\u00f3n IP", "type": "Tipo de impresora" }, "description": "Configure la integraci\u00f3n de la impresora Brother. Si tiene problemas con la configuraci\u00f3n, vaya a: https://www.home-assistant.io/integrations/brother", diff --git a/homeassistant/components/brother/translations/no.json b/homeassistant/components/brother/translations/no.json index 0235c4d1693..51c75ce779f 100644 --- a/homeassistant/components/brother/translations/no.json +++ b/homeassistant/components/brother/translations/no.json @@ -16,7 +16,7 @@ "host": "Vertsnavn eller IP-adresse til skriveren", "type": "Skriver type" }, - "description": "Konfigurer Brother skriver integrasjonen. Hvis du har problemer med konfigurasjonen, bes\u00f8k dokumentasjonen her: https://www.home-assistant.io/integrations/brother", + "description": "Sett opp Brother skriver integrasjonen. Hvis du har problemer med konfigurasjonen, bes\u00f8k dokumentasjonen her: https://www.home-assistant.io/integrations/brother", "title": "Brother skriver" }, "zeroconf_confirm": { diff --git a/homeassistant/components/cert_expiry/translations/es-419.json b/homeassistant/components/cert_expiry/translations/es-419.json index 772e37e25c8..2809d3c2899 100644 --- a/homeassistant/components/cert_expiry/translations/es-419.json +++ b/homeassistant/components/cert_expiry/translations/es-419.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured": "Esta combinaci\u00f3n de host y puerto ya est\u00e1 configurada", "import_failed": "La importaci\u00f3n desde la configuraci\u00f3n fall\u00f3" }, "error": { + "connection_refused": "Conexi\u00f3n rechazada al conectarse al host", "connection_timeout": "Tiempo de espera al conectarse a este host", "resolve_failed": "Este host no puede resolverse" }, diff --git a/homeassistant/components/climate/translations/es-419.json b/homeassistant/components/climate/translations/es-419.json index d61483edda2..569d5766f74 100644 --- a/homeassistant/components/climate/translations/es-419.json +++ b/homeassistant/components/climate/translations/es-419.json @@ -1,10 +1,17 @@ { "device_automation": { "action_type": { - "set_hvac_mode": "Cambiar el modo HVAC en {entity_name}" + "set_hvac_mode": "Cambiar el modo HVAC en {entity_name}", + "set_preset_mode": "Cambiar el ajuste preestablecido en el valor de {entity_name}" }, "condition_type": { - "is_hvac_mode": "{entity_name} est\u00e1 configurado en un modo HVAC espec\u00edfico" + "is_hvac_mode": "{entity_name} est\u00e1 configurado en un modo HVAC espec\u00edfico", + "is_preset_mode": "{entity_name} est\u00e1 configurado en un modo preestablecido espec\u00edfico" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} ha cambiado la humedad medida", + "current_temperature_changed": "{entity_name} ha cambiado la temperatura medida", + "hvac_mode_changed": "{entity_name} modo HVAC cambi\u00f3" } }, "state": { diff --git a/homeassistant/components/coolmaster/translations/es-419.json b/homeassistant/components/coolmaster/translations/es-419.json index e1da9263a0c..8cdf9675fd2 100644 --- a/homeassistant/components/coolmaster/translations/es-419.json +++ b/homeassistant/components/coolmaster/translations/es-419.json @@ -1,8 +1,13 @@ { "config": { + "error": { + "connection_error": "Error al conectarse a la instancia de CoolMasterNet. Por favor revise su host.", + "no_units": "No se encontraron unidades de HVAC en el host CoolMasterNet." + }, "step": { "user": { "data": { + "host": "Host", "off": "Puede ser apagado" }, "title": "Configure los detalles de su conexi\u00f3n CoolMasterNet." diff --git a/homeassistant/components/coronavirus/translations/es-419.json b/homeassistant/components/coronavirus/translations/es-419.json new file mode 100644 index 00000000000..1a1139a8f31 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/es-419.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Este pa\u00eds ya est\u00e1 configurado." + }, + "step": { + "user": { + "data": { + "country": "Pa\u00eds" + }, + "title": "Seleccione un pa\u00eds para monitorear" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cover/translations/es-419.json b/homeassistant/components/cover/translations/es-419.json index 3593ba28960..c6f9f7db7dd 100644 --- a/homeassistant/components/cover/translations/es-419.json +++ b/homeassistant/components/cover/translations/es-419.json @@ -1,4 +1,30 @@ { + "device_automation": { + "action_type": { + "close": "Cerrar {entity_name}", + "close_tilt": "Cerrar la inclinaci\u00f3n de {entity_name}", + "open": "Abrir {entity_name}", + "open_tilt": "Abrir la inclinaci\u00f3n de {entity_name}", + "set_position": "Establecer la posici\u00f3n de {entity_name}", + "set_tilt_position": "Establecer la posici\u00f3n de inclinaci\u00f3n {entity_name}" + }, + "condition_type": { + "is_closed": "{entity_name} est\u00e1 cerrado", + "is_closing": "{entity_name} se est\u00e1 cerrando", + "is_open": "{entity_name} est\u00e1 abierto", + "is_opening": "{entity_name} se est\u00e1 abriendo", + "is_position": "La posici\u00f3n actual de {entity_name} es", + "is_tilt_position": "La posici\u00f3n de inclinaci\u00f3n actual de {entity_name} es" + }, + "trigger_type": { + "closed": "{entity_name} cerrado", + "closing": "{entity_name} cerrando", + "opened": "{entity_name} abierto", + "opening": "{entity_name} abriendo", + "position": "Cambios de posici\u00f3n de {entity_name}", + "tilt_position": "Cambios en la posici\u00f3n de inclinaci\u00f3n cambi\u00f3 de {entity_name}" + } + }, "state": { "_": { "closed": "Cerrado", diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index 8208e2578b0..208616b7ebe 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -11,6 +11,7 @@ "error": { "no_key": "No se pudo obtener una clave de API" }, + "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}?", @@ -31,28 +32,83 @@ }, "manual_input": { "data": { + "host": "Host", "port": "Puerto" - } + }, + "title": "Configurar la puerta de enlace deCONZ" + }, + "user": { + "data": { + "host": "Seleccione la puerta de enlace descubierta deCONZ" + }, + "title": "Seleccione la puerta de enlace deCONZ" } } }, "device_automation": { "trigger_subtype": { "both_buttons": "Ambos botones", + "bottom_buttons": "Botones inferiores", "button_1": "Primer bot\u00f3n", "button_2": "Segundo bot\u00f3n", "button_3": "Tercer bot\u00f3n", "button_4": "Cuarto bot\u00f3n", "close": "Cerrar", + "dim_down": "Bajar la intensidad", + "dim_up": "Aumentar intensidad", "left": "Izquierda", "open": "Abrir", "right": "Derecha", + "side_1": "Lado 1", + "side_2": "Lado 2", + "side_3": "Lado 3", + "side_4": "Lado 4", + "side_5": "Lado 5", + "side_6": "Lado 6", + "top_buttons": "Botones superiores", "turn_off": "Apagar", "turn_on": "Encender" }, "trigger_type": { + "remote_awakened": "Dispositivo despertado", + "remote_button_double_press": "El bot\u00f3n \"{subtype}\" fue presionado 2 veces", + "remote_button_long_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado continuamente", + "remote_button_long_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\" despu\u00e9s de una pulsaci\u00f3n prolongada", + "remote_button_quadruple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 4 veces", + "remote_button_quintuple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 5 veces", "remote_button_rotated": "Bot\u00f3n girado \"{subtype}\"", - "remote_gyro_activated": "Dispositivo agitado" + "remote_button_rotation_stopped": "Se detuvo la rotaci\u00f3n del bot\u00f3n \"{subtype}\"", + "remote_button_short_press": "Se presion\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_button_short_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_button_triple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 3 veces", + "remote_double_tap": "Dispositivo \"{subtype}\" doble toque", + "remote_double_tap_any_side": "Dispositivo con doble toque en cualquier lado", + "remote_falling": "Dispositivo en ca\u00edda libre", + "remote_flip_180_degrees": "Dispositivo volteado 180 grados", + "remote_flip_90_degrees": "Dispositivo volteado 90 grados", + "remote_gyro_activated": "Dispositivo agitado", + "remote_moved": "Dispositivo movido con \"{subtype}\" arriba", + "remote_moved_any_side": "Dispositivo movido con cualquier lado hacia arriba", + "remote_rotate_from_side_1": "Dispositivo girado de \"lado 1\" a \"{subtype}\"", + "remote_rotate_from_side_2": "Dispositivo girado del \"lado 2\" al \"{subtype}\"", + "remote_rotate_from_side_3": "Dispositivo girado del \"lado 3\" al \"{subtype}\"", + "remote_rotate_from_side_4": "Dispositivo girado del \"lado 4\" al \"{subtype}\"", + "remote_rotate_from_side_5": "Dispositivo girado del \"lado 5\" al \"{subtype}\"", + "remote_rotate_from_side_6": "Dispositivo girado de \"lado 6\" a \"{subtype}\"", + "remote_turned_clockwise": "Dispositivo girado en sentido de las agujas del reloj", + "remote_turned_counter_clockwise": "Dispositivo girado en sentido contrario a las agujas del reloj" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_clip_sensor": "Permitir sensores deCONZ CLIP", + "allow_deconz_groups": "Permitir grupos de luz deCONZ" + }, + "description": "Configurar la visibilidad de los tipos de dispositivos deCONZ", + "title": "Opciones de deCONZ" + } } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 5ef7c0cc5d9..3299ecbdc55 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "El puente ya esta configurado", - "already_in_progress": "El flujo de configuraci\u00f3n para el puente ya est\u00e1 en curso.", - "no_bridges": "No se han descubierto puentes deCONZ", - "not_deconz_bridge": "No es un puente deCONZ", + "already_configured": "La pasarela ya est\u00e1 configurada", + "already_in_progress": "La configuraci\u00f3n del flujo para la pasarela ya est\u00e1 en curso.", + "no_bridges": "No se han descubierto pasarelas deCONZ", + "not_deconz_bridge": "No es una pasarela deCONZ", "one_instance_only": "El componente solo admite una instancia de deCONZ", "updated_instance": "Instancia deCONZ actualizada con nueva direcci\u00f3n de host" }, diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index e1587a07957..5cdfc70959d 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -11,7 +11,7 @@ "error": { "no_key": "Kunne ikke f\u00e5 en API-n\u00f8kkel" }, - "flow_title": "", + "flow_title": "deCONZ Zigbee gateway ({host})", "step": { "hassio_confirm": { "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegget {addon} ?", diff --git a/homeassistant/components/demo/translations/es-419.json b/homeassistant/components/demo/translations/es-419.json index a9abb4aacd9..8057621520a 100644 --- a/homeassistant/components/demo/translations/es-419.json +++ b/homeassistant/components/demo/translations/es-419.json @@ -1,3 +1,20 @@ { + "options": { + "step": { + "options_1": { + "data": { + "bool": "Booleano opcional", + "int": "Entrada num\u00e9rica" + } + }, + "options_2": { + "data": { + "multi": "Selecci\u00f3n m\u00faltiple", + "select": "Seleccione una opci\u00f3n", + "string": "Valor de cadena" + } + } + } + }, "title": "Demo" } \ No newline at end of file diff --git a/homeassistant/components/directv/translations/es-419.json b/homeassistant/components/directv/translations/es-419.json new file mode 100644 index 00000000000..6db50cd6b5a --- /dev/null +++ b/homeassistant/components/directv/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El receptor de DirecTV ya est\u00e1 configurado", + "unknown": "Error inesperado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente" + }, + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "\u00bfDesea configurar {name}?", + "title": "Conectarse al receptor DirecTV" + }, + "user": { + "data": { + "host": "Host o direcci\u00f3n IP" + }, + "title": "Conectarse al receptor DirecTV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/es-419.json b/homeassistant/components/doorbird/translations/es-419.json index 1a412b38246..5ac195e007f 100644 --- a/homeassistant/components/doorbird/translations/es-419.json +++ b/homeassistant/components/doorbird/translations/es-419.json @@ -1,19 +1,25 @@ { "config": { "abort": { + "already_configured": "Este DoorBird ya est\u00e1 configurado", + "link_local_address": "Las direcciones locales de enlace no son compatibles", "not_doorbird_device": "Este dispositivo no es un DoorBird" }, "error": { "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", "unknown": "Error inesperado" }, + "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { + "host": "Host (direcci\u00f3n IP)", "name": "Nombre del dispositivo", "password": "Contrase\u00f1a", "username": "Nombre de usuario" - } + }, + "title": "Conectar con DoorBird" } } }, diff --git a/homeassistant/components/ecobee/translations/es-419.json b/homeassistant/components/ecobee/translations/es-419.json index ff9c1f53dec..50eab590a1c 100644 --- a/homeassistant/components/ecobee/translations/es-419.json +++ b/homeassistant/components/ecobee/translations/es-419.json @@ -9,12 +9,15 @@ }, "step": { "authorize": { - "description": "Autorice esta aplicaci\u00f3n en https://www.ecobee.com/consumerportal/index.html con c\u00f3digo PIN: \n\n {pin} \n \n Luego, presione Enviar." + "description": "Autorice esta aplicaci\u00f3n en https://www.ecobee.com/consumerportal/index.html con c\u00f3digo PIN: \n\n {pin} \n \n Luego, presione Enviar.", + "title": "Autorizar aplicaci\u00f3n en ecobee.com" }, "user": { "data": { "api_key": "Clave API" - } + }, + "description": "Ingrese la clave API obtenida de ecobee.com.", + "title": "Clave API ecobee" } } } diff --git a/homeassistant/components/elgato/translations/es-419.json b/homeassistant/components/elgato/translations/es-419.json index 46a008009b9..9d12537851d 100644 --- a/homeassistant/components/elgato/translations/es-419.json +++ b/homeassistant/components/elgato/translations/es-419.json @@ -1,14 +1,24 @@ { "config": { + "abort": { + "already_configured": "Este dispositivo Elgato Key Light ya est\u00e1 configurado.", + "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." + }, + "error": { + "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." + }, + "flow_title": "Elgato Key Light: {serial_number}", "step": { "user": { "data": { "host": "Host o direcci\u00f3n IP", "port": "N\u00famero de puerto" }, - "description": "Configure su Elgato Key Light para integrarse con Home Assistant." + "description": "Configure su Elgato Key Light para integrarse con Home Assistant.", + "title": "Vincule su Elgato Key Light" }, "zeroconf_confirm": { + "description": "\u00bfDesea agregar el disposiivo Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", "title": "Dispositivo Elgato Key Light descubierto" } } diff --git a/homeassistant/components/elgato/translations/no.json b/homeassistant/components/elgato/translations/no.json index 34a9bfed772..2d60155cbb8 100644 --- a/homeassistant/components/elgato/translations/no.json +++ b/homeassistant/components/elgato/translations/no.json @@ -7,7 +7,7 @@ "error": { "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." }, - "flow_title": "", + "flow_title": "Elgato Key Light: {serial_number}", "step": { "user": { "data": { diff --git a/homeassistant/components/elkm1/translations/es-419.json b/homeassistant/components/elkm1/translations/es-419.json new file mode 100644 index 00000000000..02271c4ea6c --- /dev/null +++ b/homeassistant/components/elkm1/translations/es-419.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "address_already_configured": "Un ElkM1 con esta direcci\u00f3n ya est\u00e1 configurado", + "already_configured": "Un ElkM1 con este prefijo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "address": "La direcci\u00f3n IP, dominio o puerto serie si se conecta via serial.", + "password": "Contrase\u00f1a (solo segura).", + "prefix": "Un prefijo \u00fanico (d\u00e9jelo en blanco si solo tiene un ElkM1).", + "protocol": "Protocolo", + "temperature_unit": "La unidad de temperatura que utiliza ElkM1.", + "username": "Nombre de usuario (solo seguro)." + }, + "description": "La cadena de direcci\u00f3n debe tener el formato 'direcci\u00f3n[:puerto]' para 'seguro' y 'no seguro'. Ejemplo: '192.168.1.1'. El puerto es opcional y el valor predeterminado es 2101 para 'no seguro' y 2601 para 'seguro'. Para el protocolo serie, la direcci\u00f3n debe estar en la forma 'tty[:baudios]'. Ejemplo: '/dev/ttyS1'. La velocidad en baudios es opcional y su valor predeterminado es 115200.", + "title": "Con\u00e9ctese al control Elk-M1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/es-419.json b/homeassistant/components/emulated_roku/translations/es-419.json index 85d75c81ff3..9aa7ff0bc92 100644 --- a/homeassistant/components/emulated_roku/translations/es-419.json +++ b/homeassistant/components/emulated_roku/translations/es-419.json @@ -7,7 +7,9 @@ "user": { "data": { "host_ip": "IP del host", - "name": "Nombre" + "listen_port": "Puerto de escucha", + "name": "Nombre", + "upnp_bind_multicast": "Enlazar multidifusi\u00f3n (verdadero/falso)" }, "title": "Definir la configuraci\u00f3n del servidor." } diff --git a/homeassistant/components/esphome/translations/de.json b/homeassistant/components/esphome/translations/de.json index 64262aa654a..299660cb348 100644 --- a/homeassistant/components/esphome/translations/de.json +++ b/homeassistant/components/esphome/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP ist bereits konfiguriert" + "already_configured": "ESP ist bereits konfiguriert", + "already_in_progress": "Die ESP-Konfiguration wird bereits ausgef\u00fchrt" }, "error": { "connection_error": "Keine Verbindung zum ESP m\u00f6glich. Achte darauf, dass deine YAML-Datei eine Zeile 'api:' enth\u00e4lt.", diff --git a/homeassistant/components/esphome/translations/es-419.json b/homeassistant/components/esphome/translations/es-419.json index 7bbe61aceb2..2774ff7ea68 100644 --- a/homeassistant/components/esphome/translations/es-419.json +++ b/homeassistant/components/esphome/translations/es-419.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP ya est\u00e1 configurado" + "already_configured": "ESP ya est\u00e1 configurado", + "already_in_progress": "La configuraci\u00f3n de ESP ya est\u00e1 en progreso" }, "error": { "connection_error": "No se puede conectar a ESP. Aseg\u00farese de que su archivo YAML contenga una l\u00ednea 'api:'.", diff --git a/homeassistant/components/esphome/translations/it.json b/homeassistant/components/esphome/translations/it.json index 050c1222495..60ba2a31890 100644 --- a/homeassistant/components/esphome/translations/it.json +++ b/homeassistant/components/esphome/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "ESP \u00e8 gi\u00e0 configurato" + "already_configured": "ESP \u00e8 gi\u00e0 configurato", + "already_in_progress": "La configurazione ESP \u00e8 gi\u00e0 in corso" }, "error": { "connection_error": "Impossibile connettersi ad ESP. Assicurati che il tuo file YAML contenga una riga \"api:\".", diff --git a/homeassistant/components/esphome/translations/no.json b/homeassistant/components/esphome/translations/no.json index 37e3b881b8b..c04f0c2a09d 100644 --- a/homeassistant/components/esphome/translations/no.json +++ b/homeassistant/components/esphome/translations/no.json @@ -8,7 +8,7 @@ "invalid_password": "Ugyldig passord!", "resolve_error": "Kan ikke l\u00f8se adressen til ESP. Hvis denne feilen vedvarer, m\u00e5 du [angi en statisk IP-adresse](https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips)" }, - "flow_title": "", + "flow_title": "ESPHome: {name}", "step": { "authenticate": { "data": { diff --git a/homeassistant/components/fan/translations/es-419.json b/homeassistant/components/fan/translations/es-419.json index 6060cff985a..8e9611bdff9 100644 --- a/homeassistant/components/fan/translations/es-419.json +++ b/homeassistant/components/fan/translations/es-419.json @@ -1,5 +1,9 @@ { "device_automation": { + "action_type": { + "turn_off": "Desactivar {entity_name}", + "turn_on": "Activar {entity_name}" + }, "condition_type": { "is_off": "{entity_name} est\u00e1 apagado", "is_on": "{entity_name} est\u00e1 encendido" diff --git a/homeassistant/components/flume/translations/es-419.json b/homeassistant/components/flume/translations/es-419.json new file mode 100644 index 00000000000..026875846c6 --- /dev/null +++ b/homeassistant/components/flume/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Esta cuenta ya est\u00e1 configurada" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "client_id": "Identificaci\u00f3n del cliente", + "client_secret": "Secreto del cliente", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Para acceder a la API personal de Flume, deber\u00e1 solicitar un 'ID de cliente' y un 'Secreto de cliente' en https://portal.flumetech.com/settings#token", + "title": "Con\u00e9ctese a su cuenta Flume" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/es-419.json b/homeassistant/components/flunearyou/translations/es-419.json new file mode 100644 index 00000000000..9626a509bca --- /dev/null +++ b/homeassistant/components/flunearyou/translations/es-419.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Estas coordenadas ya est\u00e1n registradas." + }, + "error": { + "general_error": "Se ha producido un error desconocido." + }, + "step": { + "user": { + "data": { + "latitude": "Latitud", + "longitude": "Longitud" + }, + "description": "Monitoree los repotes basados en el usuario y los CDC para un par de coordenadas.", + "title": "Configurar Flu Near You (Gripe cerca de usted)" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/es-419.json b/homeassistant/components/freebox/translations/es-419.json new file mode 100644 index 00000000000..01583551453 --- /dev/null +++ b/homeassistant/components/freebox/translations/es-419.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "Host ya configurado" + }, + "error": { + "connection_failed": "No se pudo conectar, intente nuevamente", + "register_failed": "No se pudo registrar, intente de nuevo", + "unknown": "Error desconocido: vuelva a intentarlo m\u00e1s tarde" + }, + "step": { + "link": { + "description": "Haga clic en \"Enviar\", luego toque la flecha derecha en el enrutador para registrar Freebox con Home Assistant. \n\n![Location of button on the router](/static/images/config_freebox.png)", + "title": "Enlazar enrutador Freebox" + }, + "user": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "title": "Freebox" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/es-419.json b/homeassistant/components/fritzbox/translations/es-419.json new file mode 100644 index 00000000000..4e8003a06d8 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/es-419.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Este AVM FRITZ!Box ya est\u00e1 configurado.", + "already_in_progress": "La configuraci\u00f3n de AVM FRITZ!Box ya est\u00e1 en progreso.", + "not_found": "No se encontr\u00f3 ning\u00fan AVM FRITZ!Box compatible en la red.", + "not_supported": "Conectado a AVM FRITZ!Box pero no puede controlar dispositivos Smart Home." + }, + "error": { + "auth_failed": "El nombre de usuario y/o la contrase\u00f1a son incorrectos." + }, + "flow_title": "AVM FRITZ!Box: {name}", + "step": { + "confirm": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "\u00bfDesea configurar {name}?", + "title": "AVM FRITZ!Box" + }, + "user": { + "data": { + "host": "Host o direcci\u00f3n IP", + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Ingrese la informaci\u00f3n de su AVM FRITZ!Box.", + "title": "AVM FRITZ!Box" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index 5072d62374b..9a17f540ed1 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "not_supported": "Connect\u00e9 \u00e0 AVM FRITZ! Box mais impossible de contr\u00f4ler les appareils Smart Home." + }, "error": { "auth_failed": "Le nom d'utilisateur et / ou le mot de passe sont incorrects." }, diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index 85027c50af9..284782755bd 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -9,7 +9,7 @@ "error": { "auth_failed": "Brukernavn og/eller passord er feil." }, - "flow_title": "", + "flow_title": "AVM FRITZ!Box: {name}", "step": { "confirm": { "data": { diff --git a/homeassistant/components/garmin_connect/translations/es-419.json b/homeassistant/components/garmin_connect/translations/es-419.json index 6e20b4cd2cc..42263ce0780 100644 --- a/homeassistant/components/garmin_connect/translations/es-419.json +++ b/homeassistant/components/garmin_connect/translations/es-419.json @@ -15,7 +15,8 @@ "password": "Contrase\u00f1a", "username": "Nombre de usuario" }, - "description": "Ingrese sus credenciales." + "description": "Ingrese sus credenciales.", + "title": "Garmin Connect" } } } diff --git a/homeassistant/components/gdacs/translations/es-419.json b/homeassistant/components/gdacs/translations/es-419.json new file mode 100644 index 00000000000..6b6999a196e --- /dev/null +++ b/homeassistant/components/gdacs/translations/es-419.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada." + }, + "step": { + "user": { + "data": { + "radius": "Radio" + }, + "title": "Complete los detalles de su filtro." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/no.json b/homeassistant/components/geofency/translations/no.json index ea9e1827b63..8e66cab4c9c 100644 --- a/homeassistant/components/geofency/translations/no.json +++ b/homeassistant/components/geofency/translations/no.json @@ -9,7 +9,7 @@ }, "step": { "user": { - "description": "Er du sikker p\u00e5 at du vil konfigurere Geofency Webhook?", + "description": "Er du sikker p\u00e5 at du vil sette opp Geofency Webhook?", "title": "Sett opp Geofency Webhook" } } diff --git a/homeassistant/components/geonetnz_quakes/translations/es-419.json b/homeassistant/components/geonetnz_quakes/translations/es-419.json new file mode 100644 index 00000000000..7afffd7bb97 --- /dev/null +++ b/homeassistant/components/geonetnz_quakes/translations/es-419.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "La ubicaci\u00f3n ya est\u00e1 configurada." + }, + "step": { + "user": { + "data": { + "mmi": "MMI", + "radius": "Radio" + }, + "title": "Complete los detalles de su filtro." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_volcano/translations/es-419.json b/homeassistant/components/geonetnz_volcano/translations/es-419.json new file mode 100644 index 00000000000..c26033e1861 --- /dev/null +++ b/homeassistant/components/geonetnz_volcano/translations/es-419.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "identifier_exists": "Lugar ya registrado" + }, + "step": { + "user": { + "data": { + "radius": "Radio" + }, + "title": "Complete los detalles de su filtro." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gios/translations/es-419.json b/homeassistant/components/gios/translations/es-419.json index 53439a7ab7b..848247bdf75 100644 --- a/homeassistant/components/gios/translations/es-419.json +++ b/homeassistant/components/gios/translations/es-419.json @@ -1,10 +1,21 @@ { "config": { + "abort": { + "already_configured": "La integraci\u00f3n de GIO\u015a para esta estaci\u00f3n de medici\u00f3n ya est\u00e1 configurada." + }, + "error": { + "cannot_connect": "No se puede conectar al servidor GIO\u015a.", + "invalid_sensors_data": "Datos de sensores no v\u00e1lidos para esta estaci\u00f3n de medici\u00f3n.", + "wrong_station_id": "La identificaci\u00f3n de la estaci\u00f3n de medici\u00f3n no es correcta." + }, "step": { "user": { "data": { - "name": "Nombre de la integraci\u00f3n" - } + "name": "Nombre de la integraci\u00f3n", + "station_id": "Identificaci\u00f3n de la estaci\u00f3n de medici\u00f3n" + }, + "description": "Establecer la integraci\u00f3n de la calidad del aire GIO\u015a (Inspecci\u00f3n Jefe de Protecci\u00f3n Ambiental de Polonia). Si necesita ayuda con la configuraci\u00f3n, eche un vistazo aqu\u00ed: https://www.home-assistant.io/integrations/gios", + "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)" } } } diff --git a/homeassistant/components/glances/translations/es-419.json b/homeassistant/components/glances/translations/es-419.json index 6debc6da6c1..5e060b20d47 100644 --- a/homeassistant/components/glances/translations/es-419.json +++ b/homeassistant/components/glances/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "El host ya est\u00e1 configurado." + }, "error": { "cannot_connect": "No se puede conectar al host", "wrong_version": "Versi\u00f3n no compatible (2 o 3 solamente)" @@ -7,13 +10,16 @@ "step": { "user": { "data": { + "host": "Host", "name": "Nombre", "password": "Contrase\u00f1a", "port": "Puerto", + "ssl": "Use SSL/TLS para conectarse al sistema Glances", "username": "Nombre de usuario", "verify_ssl": "Verificar la certificaci\u00f3n del sistema", "version": "Versi\u00f3n de API de Glances (2 o 3)" - } + }, + "title": "Configurar Glances" } } }, diff --git a/homeassistant/components/griddy/translations/es-419.json b/homeassistant/components/griddy/translations/es-419.json new file mode 100644 index 00000000000..652c8484b4e --- /dev/null +++ b/homeassistant/components/griddy/translations/es-419.json @@ -0,0 +1,20 @@ +{ + "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/hangouts/translations/es-419.json b/homeassistant/components/hangouts/translations/es-419.json index 9ff97592d91..a8ae41ec21e 100644 --- a/homeassistant/components/hangouts/translations/es-419.json +++ b/homeassistant/components/hangouts/translations/es-419.json @@ -6,6 +6,7 @@ }, "error": { "invalid_2fa": "Autenticaci\u00f3n de 2 factores no v\u00e1lida, intente nuevamente.", + "invalid_2fa_method": "M\u00e9todo 2FA no v\u00e1lido (verificar en el tel\u00e9fono).", "invalid_login": "Inicio de sesi\u00f3n no v\u00e1lido, por favor, int\u00e9ntalo de nuevo." }, "step": { diff --git a/homeassistant/components/harmony/translations/es-419.json b/homeassistant/components/harmony/translations/es-419.json new file mode 100644 index 00000000000..83781be522e --- /dev/null +++ b/homeassistant/components/harmony/translations/es-419.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "\u00bfDesea configurar {name} ({host})?", + "title": "Configurar Logitech Harmony Hub" + }, + "user": { + "data": { + "host": "Nombre de host o direcci\u00f3n IP", + "name": "Nombre del concentrador" + }, + "title": "Configurar Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "La actividad predeterminada para ejecutar cuando no se especifica ninguno.", + "delay_secs": "El retraso entre el env\u00edo de comandos." + }, + "description": "Ajuste las opciones de Harmony Hub" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/no.json b/homeassistant/components/harmony/translations/no.json index 871b3161fcf..9cae0663208 100644 --- a/homeassistant/components/harmony/translations/no.json +++ b/homeassistant/components/harmony/translations/no.json @@ -7,7 +7,7 @@ "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", "unknown": "Uventet feil" }, - "flow_title": "", + "flow_title": "Logitech Harmony Hub {name}", "step": { "link": { "description": "Vil du konfigurere {name} ({host})?", diff --git a/homeassistant/components/heos/translations/es-419.json b/homeassistant/components/heos/translations/es-419.json index 01338dc5af3..902f65bf5e1 100644 --- a/homeassistant/components/heos/translations/es-419.json +++ b/homeassistant/components/heos/translations/es-419.json @@ -8,6 +8,10 @@ }, "step": { "user": { + "data": { + "access_token": "Host", + "host": "Host" + }, "description": "Ingrese el nombre de host o la direcci\u00f3n IP de un dispositivo Heos (preferiblemente uno conectado por cable a la red).", "title": "Con\u00e9ctate a Heos" } diff --git a/homeassistant/components/hisense_aehw4a1/translations/es-419.json b/homeassistant/components/hisense_aehw4a1/translations/es-419.json new file mode 100644 index 00000000000..c9c4270360a --- /dev/null +++ b/homeassistant/components/hisense_aehw4a1/translations/es-419.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "No se encontraron dispositivos Hisense AEH-W4A1 en la red.", + "single_instance_allowed": "Solo es posible una \u00fanica configuraci\u00f3n de Hisense AEH-W4A1." + }, + "step": { + "confirm": { + "description": "\u00bfDesea configurar Hisense AEH-W4A1?", + "title": "Hisense AEH-W4A1" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hisense_aehw4a1/translations/no.json b/homeassistant/components/hisense_aehw4a1/translations/no.json index 0b0bf55d7af..bc048ef2286 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/no.json +++ b/homeassistant/components/hisense_aehw4a1/translations/no.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere Hisense AEH-W4A1?", + "description": "Vil du sette opp Hisense AEH-W4A1?", "title": "" } } diff --git a/homeassistant/components/homekit/translations/de.json b/homeassistant/components/homekit/translations/de.json new file mode 100644 index 00000000000..fe4fc52c695 --- /dev/null +++ b/homeassistant/components/homekit/translations/de.json @@ -0,0 +1,29 @@ +{ + "config": { + "step": { + "user": { + "data": { + "include_domains": "Einzubeziehende Domains" + } + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "safe_mode": "Abgesicherter Modus (nur aktivieren, wenn das Pairing fehlschl\u00e4gt)" + }, + "title": "Erweiterte Konfiguration" + }, + "exclude": { + "data": { + "exclude_entities": "Auszuschlie\u00dfende Entit\u00e4ten" + } + }, + "yaml": { + "description": "Dieser Eintrag wird \u00fcber YAML gesteuert" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 08ebfa44c45..8d43341c61b 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -1,17 +1,33 @@ { - "title": "HomeKit Bridge", + "config": { + "abort": { + "port_name_in_use": "A bridge with the same name or port is already configured." + }, + "step": { + "pairing": { + "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d.", + "title": "Pair HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "include_domains": "Domains to include" + }, + "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", + "title": "Activate HomeKit Bridge" + } + } + }, "options": { "step": { - "yaml": { - "title": "Adjust HomeKit Bridge Options", - "description": "This entry is controlled via YAML" - }, - "init": { + "advanced": { "data": { - "include_domains": "[%key:component::homekit::config::step::user::data::include_domains%]" + "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", + "safe_mode": "Safe Mode (enable only if pairing fails)", + "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" }, - "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", - "title": "Select domains to bridge." + "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", + "title": "Advanced Configuration" }, "exclude": { "data": { @@ -20,34 +36,18 @@ "description": "Choose the entities that you do NOT want to be bridged.", "title": "Exclude entities in selected domains from bridge" }, - "advanced": { + "init": { "data": { - "auto_start": "[%key:component::homekit::config::step::user::data::auto_start%]", - "safe_mode": "Safe Mode (enable only if pairing fails)", - "zeroconf_default_interface": "Use default zeroconf interface (enable if the bridge cannot be found in the Home app)" + "include_domains": "Domains to include" }, - "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", - "title": "Advanced Configuration" + "description": "Entities in the \u201cDomains to include\u201d will be bridged to HomeKit. You will be able to select which entities to exclude from this list on the next screen.", + "title": "Select domains to bridge." + }, + "yaml": { + "description": "This entry is controlled via YAML", + "title": "Adjust HomeKit Bridge Options" } } }, - "config": { - "step": { - "user": { - "data": { - "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "include_domains": "Domains to include" - }, - "description": "A HomeKit Bridge will allow you to access your Home Assistant entities in HomeKit. HomeKit Bridges are limited to 150 accessories per instance including the bridge itself. If you wish to bridge more than the maximum number of accessories, it is recommended that you use multiple HomeKit bridges for different domains. Detailed entity configuration is only available via YAML for the primary bridge.", - "title": "Activate HomeKit Bridge" - }, - "pairing": { - "title": "Pair HomeKit Bridge", - "description": "As soon as the {name} bridge is ready, pairing will be available in \u201cNotifications\u201d as \u201cHomeKit Bridge Setup\u201d." - } - }, - "abort": { - "port_name_in_use": "A bridge with the same name or port is already configured." - } - } -} + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/es-419.json b/homeassistant/components/homekit/translations/es-419.json new file mode 100644 index 00000000000..a3f1b1acc17 --- /dev/null +++ b/homeassistant/components/homekit/translations/es-419.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Un puente con el mismo nombre o puerto ya est\u00e1 configurado." + }, + "step": { + "pairing": { + "description": "Tan pronto como el puente {name} est\u00e9 listo, el emparejamiento estar\u00e1 disponible en \"Notificaciones\" como \"Configuraci\u00f3n del puente HomeKit\".", + "title": "Emparejar HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "Inicio autom\u00e1tico (deshabilitar si se usa Z-Wave u otro sistema de inicio diferido)", + "include_domains": "Dominios para incluir" + }, + "description": "Un HomeKit Bridge le permitir\u00e1 acceder a sus entidades de Home Assistant en HomeKit. Los puentes HomeKit est\u00e1n limitados a 150 accesorios por instancia, incluido el puente mismo. Si desea unir m\u00e1s de la cantidad m\u00e1xima de accesorios, se recomienda que use m\u00faltiples puentes HomeKit para diferentes dominios. La configuraci\u00f3n detallada de la entidad solo est\u00e1 disponible a trav\u00e9s de YAML para el puente primario.", + "title": "Activar HomeKit Bridge" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Inicio autom\u00e1tico (deshabilitar si se usa Z-Wave u otro sistema de inicio diferido)", + "safe_mode": "Modo seguro (habil\u00edtelo solo si falla el emparejamiento)", + "zeroconf_default_interface": "Use la interfaz zeroconf predeterminada (habil\u00edtela si no se puede encontrar el puente en la aplicaci\u00f3n Inicio)" + }, + "description": "Esta configuraci\u00f3n solo necesita ser ajustada si el puente HomeKit no es funcional.", + "title": "Configuraci\u00f3n avanzada" + }, + "exclude": { + "data": { + "exclude_entities": "Entidades a excluir" + }, + "description": "Seleccione las entidades que NO desea puentear.", + "title": "Excluir entidades en dominios seleccionados del puente" + }, + "init": { + "data": { + "include_domains": "Dominios para incluir" + }, + "description": "Las entidades en los \"Dominios para incluir\" se vincular\u00e1n a HomeKit. Podr\u00e1 seleccionar qu\u00e9 entidades excluir de esta lista en la siguiente pantalla.", + "title": "Seleccione dominios para puentear." + }, + "yaml": { + "description": "Esta entrada se controla a trav\u00e9s de YAML", + "title": "Ajuste las opciones de puente de HomeKit" + } + } + }, + "title": "Puente HomeKit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/es.json b/homeassistant/components/homekit/translations/es.json new file mode 100644 index 00000000000..7e5b7477a60 --- /dev/null +++ b/homeassistant/components/homekit/translations/es.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Ya est\u00e1 configurada una pasarela con el mismo nombre o puerto." + }, + "step": { + "pairing": { + "description": "Tan pronto como la pasarela {name} est\u00e9 lista, la vinculaci\u00f3n estar\u00e1 disponible en \"Notificaciones\" como \"configuraci\u00f3n de pasarela Homekit\"", + "title": "Vincular pasarela Homekit" + }, + "user": { + "description": "Una pasarela Homekit permitir\u00e1 a Homekit acceder a sus entidades de Home Assistant. La pasarela Homekit est\u00e1 limitada a 150 accesorios por instancia incluyendo la propia pasarela. Si desea enlazar m\u00e1s del m\u00e1ximo n\u00famero de accesorios, se recomienda que use multiples pasarelas Homekit para diferentes dominios. Configuraci\u00f3n detallada de la entidad solo est\u00e1 disponible via YAML para la pasarela primaria.", + "title": "Activar pasarela Homekit" + } + } + }, + "title": "Pasarela Homekit" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/it.json b/homeassistant/components/homekit/translations/it.json new file mode 100644 index 00000000000..9ca0b69165c --- /dev/null +++ b/homeassistant/components/homekit/translations/it.json @@ -0,0 +1,53 @@ +{ + "config": { + "abort": { + "port_name_in_use": "Un bridge con lo stesso nome o porta \u00e8 gi\u00e0 configurato." + }, + "step": { + "pairing": { + "description": "Non appena il bridge {name} \u00e8 pronto, l'associazione sar\u00e0 disponibile in \"Notifiche\" come \"HomeKit Bridge Setup\".", + "title": "Associa HomeKit Bridge" + }, + "user": { + "data": { + "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", + "include_domains": "Domini da includere" + }, + "description": "Un HomeKit Bridge ti permetter\u00e0 di accedere alle tue entit\u00e0 Home Assistant in HomeKit. Gli HomeKit Bridges sono limitati a 150 accessori per istanza, incluso il bridge stesso. Se si desidera collegare un numero di accessori superiore al massimo consentito, si consiglia di utilizzare pi\u00f9 HomeKit Bridge per domini diversi. La configurazione dettagliata delle entit\u00e0 \u00e8 disponibile solo tramite YAML per il bridge primario.", + "title": "Attiva HomeKit Bridge" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "Avvio automatico (disabilitare se si utilizza Z-Wave o un altro sistema di avvio ritardato)", + "safe_mode": "Modalit\u00e0 provvisoria (attivare solo in caso di errore di associazione)", + "zeroconf_default_interface": "Utilizzare l'interfaccia zeroconf predefinita (abilitare se il bridge non pu\u00f2 essere trovato nell'app Home)" + }, + "description": "Queste impostazioni devono essere modificate solo se il bridge HomeKit non funziona.", + "title": "Configurazione Avanzata" + }, + "exclude": { + "data": { + "exclude_entities": "Entit\u00e0 da escludere" + }, + "description": "Scegliere le entit\u00e0 che NON si desidera collegare.", + "title": "Escludere le entit\u00e0 dal bridge nei domini selezionati " + }, + "init": { + "data": { + "include_domains": "Domini da includere" + }, + "description": "Le entit\u00e0 nei \"Domini da includere\" saranno collegate a HomeKit. Sarai in grado di selezionare quali entit\u00e0 escludere da questo elenco nella schermata successiva.", + "title": "Selezionare i domini al bridge." + }, + "yaml": { + "description": "Questa voce \u00e8 controllata tramite YAML", + "title": "Regolare le opzioni di HomeKit Bridge" + } + } + }, + "title": "HomeKit Bridge" +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/zh-Hans.json b/homeassistant/components/homekit/translations/zh-Hans.json new file mode 100644 index 00000000000..c47a6fe104f --- /dev/null +++ b/homeassistant/components/homekit/translations/zh-Hans.json @@ -0,0 +1,49 @@ +{ + "config": { + "abort": { + "port_name_in_use": "\u5df2\u914d\u7f6e\u8fc7\u5177\u6709\u76f8\u540c\u540d\u79f0\u6216\u7aef\u53e3\u7684\u6865\u63a5\u5668\u3002" + }, + "step": { + "pairing": { + "description": "\u4e00\u65e6\u6865\u63a5\u5668 {name} \u51c6\u5907\u5c31\u7eea\uff0c\u5c31\u53ef\u4ee5\u5728\u201c\u901a\u77e5\u201d\u627e\u5230\u201cHomeKit \u6865\u63a5\u5668\u914d\u7f6e\u201d\u8fdb\u884c\u914d\u5bf9\u3002", + "title": "\u914d\u5bf9 HomeKit \u6865\u63a5\u5668" + }, + "user": { + "data": { + "auto_start": "\u81ea\u52a8\u542f\u52a8\uff08\u5982\u679c\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u8fdf\u542f\u52a8\u7cfb\u7edf\uff0c\u8bf7\u7981\u7528\u6b64\u9879\uff09", + "include_domains": "\u8981\u5305\u542b\u7684\u57df" + }, + "description": "HomeKit \u6865\u63a5\u5668\u53ef\u4ee5\u8ba9\u60a8\u901a\u8fc7 HomeKit \u8bbf\u95ee Home Assistant \u4e2d\u7684\u5b9e\u4f53\u3002\u6bcf\u4e2a\u6865\u63a5\u5668\u5b9e\u4f8b\u6700\u591a\u53ef\u6a21\u62df 150 \u4e2a\u914d\u4ef6\uff0c\u5305\u62ec\u6865\u63a5\u5668\u672c\u8eab\u3002\u5982\u679c\u60a8\u5e0c\u671b\u6865\u63a5\u7684\u914d\u4ef6\u591a\u4e8e\u6b64\u6570\u91cf\uff0c\u5efa\u8bae\u4e3a\u4e0d\u540c\u7684\u57df\u4f7f\u7528\u591a\u4e2a HomeKit \u6865\u63a5\u5668\u3002\u8be6\u7ec6\u7684\u5b9e\u4f53\u914d\u7f6e\u4ec5\u53ef\u7528\u4e8e\u4e3b\u6865\u63a5\u5668\uff0c\u4e14\u987b\u901a\u8fc7 YAML \u914d\u7f6e\u3002", + "title": "\u6fc0\u6d3b HomeKit \u6865\u63a5\u5668" + } + } + }, + "options": { + "step": { + "advanced": { + "data": { + "auto_start": "\u81ea\u52a8\u542f\u52a8\uff08\u5982\u679c\u4f7f\u7528 Z-Wave \u6216\u5176\u4ed6\u5ef6\u8fdf\u542f\u52a8\u7cfb\u7edf\uff0c\u8bf7\u7981\u7528\u6b64\u9879\uff09", + "safe_mode": "\u5b89\u5168\u6a21\u5f0f\uff08\u4ec5\u5728\u914d\u5bf9\u5931\u8d25\u65f6\u542f\u7528\uff09", + "zeroconf_default_interface": "\u4f7f\u7528\u9ed8\u8ba4\u7684 zeroconf \u63a5\u53e3\uff08\u5982\u679c\u5728\u201c\u5bb6\u5ead\u201d\u5e94\u7528\u7a0b\u5e8f\u4e2d\u627e\u4e0d\u5230\u6865\u63a5\u5668\u5219\u542f\u7528\uff09" + }, + "description": "\u8fd9\u4e9b\u8bbe\u7f6e\u53ea\u6709\u5f53 HomeKit \u6865\u63a5\u5668\u529f\u80fd\u4e0d\u6b63\u5e38\u65f6\u624d\u9700\u8981\u8c03\u6574\u3002", + "title": "\u9ad8\u7ea7\u914d\u7f6e" + }, + "exclude": { + "data": { + "exclude_entities": "\u8981\u6392\u9664\u7684\u5b9e\u4f53" + }, + "description": "\u9009\u62e9\u4e0d\u9700\u8981\u6865\u63a5\u7684\u5b9e\u4f53\u3002", + "title": "\u5bf9\u9009\u62e9\u7684\u57df\u6392\u9664\u5b9e\u4f53" + }, + "init": { + "data": { + "include_domains": "\u8981\u5305\u542b\u7684\u57df" + }, + "description": "\u201c\u8981\u5305\u542b\u7684\u57df\u201d\u4e2d\u7684\u5b9e\u4f53\u5c06\u88ab\u6865\u63a5\u5230 HomeKit\u3002\u5728\u4e0b\u4e00\u9875\u53ef\u4ee5\u9009\u62e9\u8981\u6392\u9664\u5176\u4e2d\u7684\u54ea\u4e9b\u5b9e\u4f53\u3002", + "title": "\u9009\u62e9\u8981\u6865\u63a5\u7684\u57df\u3002" + } + } + }, + "title": "HomeKit \u6865\u63a5\u5668" +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/es-419.json b/homeassistant/components/homekit_controller/translations/es-419.json index f3a084e7545..a10c6eaa04f 100644 --- a/homeassistant/components/homekit_controller/translations/es-419.json +++ b/homeassistant/components/homekit_controller/translations/es-419.json @@ -1,10 +1,19 @@ { "config": { "abort": { + "accessory_not_found_error": "No se puede agregar el emparejamiento ya que el dispositivo ya no se puede encontrar.", "already_configured": "El accesorio ya est\u00e1 configurado con este controlador.", - "already_paired": "Este accesorio ya est\u00e1 emparejado con otro dispositivo. Por favor, reinicie el accesorio y vuelva a intentarlo." + "already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en progreso.", + "already_paired": "Este accesorio ya est\u00e1 emparejado con otro dispositivo. Por favor, reinicie el accesorio y vuelva a intentarlo.", + "ignored_model": "El soporte de HomeKit para este modelo est\u00e1 bloqueado ya que hay disponible una caracter\u00edstica m\u00e1s completa de integraci\u00f3n nativa.", + "invalid_config_entry": "Este dispositivo se muestra como listo para emparejar, pero ya hay una entrada de configuraci\u00f3n conflictiva en Home Assistant que primero debe eliminarse.", + "no_devices": "No se encontraron dispositivos no emparejados" }, "error": { + "authentication_error": "C\u00f3digo de HomeKit incorrecto. Por favor revisalo e int\u00e9ntalo de nuevo.", + "busy_error": "El dispositivo se neg\u00f3 a agregar el emparejamiento ya que ya se est\u00e1 emparejando con otro controlador.", + "max_peers_error": "El dispositivo se neg\u00f3 a agregar emparejamiento ya que no tiene almacenamiento de emparejamiento libre.", + "max_tries_error": "El dispositivo se neg\u00f3 a agregar el emparejamiento ya que recibi\u00f3 m\u00e1s de 100 intentos de autenticaci\u00f3n fallidos.", "pairing_failed": "Se produjo un error no controlado al intentar vincularse con este dispositivo. Esto puede ser una falla temporal o su dispositivo puede no ser compatible actualmente.", "unable_to_pair": "No se puede vincular, por favor intente nuevamente.", "unknown_error": "El dispositivo inform\u00f3 un error desconocido. Vinculaci\u00f3n fallida." diff --git a/homeassistant/components/homekit_controller/translations/zh-Hans.json b/homeassistant/components/homekit_controller/translations/zh-Hans.json index 4b58e4f9fb6..a8360161c6a 100644 --- a/homeassistant/components/homekit_controller/translations/zh-Hans.json +++ b/homeassistant/components/homekit_controller/translations/zh-Hans.json @@ -3,6 +3,7 @@ "abort": { "accessory_not_found_error": "\u65e0\u6cd5\u6dfb\u52a0\u914d\u5bf9\uff0c\u56e0\u4e3a\u65e0\u6cd5\u518d\u627e\u5230\u8bbe\u5907\u3002", "already_configured": "\u914d\u4ef6\u5df2\u901a\u8fc7\u6b64\u63a7\u5236\u5668\u914d\u7f6e\u5b8c\u6210\u3002", + "already_in_progress": "\u6b64\u8bbe\u5907\u7684\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d\u3002", "already_paired": "\u6b64\u914d\u4ef6\u5df2\u4e0e\u53e6\u4e00\u53f0\u8bbe\u5907\u914d\u5bf9\u3002\u8bf7\u91cd\u7f6e\u914d\u4ef6\uff0c\u7136\u540e\u91cd\u8bd5\u3002", "ignored_model": "HomeKit \u5bf9\u6b64\u8bbe\u5907\u7684\u652f\u6301\u5df2\u88ab\u963b\u6b62\uff0c\u56e0\u4e3a\u6709\u529f\u80fd\u66f4\u5b8c\u6574\u7684\u539f\u751f\u96c6\u6210\u53ef\u4ee5\u4f7f\u7528\u3002", "invalid_config_entry": "\u6b64\u8bbe\u5907\u5df2\u51c6\u5907\u597d\u914d\u5bf9\uff0c\u4f46\u662f Home Assistant \u4e2d\u5b58\u5728\u4e0e\u4e4b\u51b2\u7a81\u7684\u914d\u7f6e\uff0c\u5fc5\u987b\u5148\u5c06\u5176\u5220\u9664\u3002", diff --git a/homeassistant/components/homematicip_cloud/translations/es-419.json b/homeassistant/components/homematicip_cloud/translations/es-419.json index a853d7677c8..1b743f1c51f 100644 --- a/homeassistant/components/homematicip_cloud/translations/es-419.json +++ b/homeassistant/components/homematicip_cloud/translations/es-419.json @@ -8,7 +8,8 @@ "error": { "invalid_pin": "PIN no v\u00e1lido, por favor intente de nuevo.", "press_the_button": "Por favor, presione el bot\u00f3n azul.", - "register_failed": "No se pudo registrar, por favor intente de nuevo." + "register_failed": "No se pudo registrar, por favor intente de nuevo.", + "timeout_button": "Tiempo de espera del bot\u00f3n azul, intente nuevamente." }, "step": { "init": { @@ -20,7 +21,8 @@ "title": "Elija el punto de acceso HomematicIP" }, "link": { - "description": "Presione el bot\u00f3n azul en el punto de acceso y el bot\u00f3n enviar para registrar HomematicIP con Home Assistant. \n\n ! [Ubicaci\u00f3n del bot\u00f3n en el puente] (/static/images/config_flows/config_homematicip_cloud.png)" + "description": "Presione el bot\u00f3n azul en el punto de acceso y el bot\u00f3n enviar para registrar HomematicIP con Home Assistant. \n\n ! [Ubicaci\u00f3n del bot\u00f3n en el puente] (/static/images/config_flows/config_homematicip_cloud.png)", + "title": "Enlazar Punto de acceso" } } } diff --git a/homeassistant/components/homematicip_cloud/translations/es.json b/homeassistant/components/homematicip_cloud/translations/es.json index a017a5a1df7..b1d1ea64e3f 100644 --- a/homeassistant/components/homematicip_cloud/translations/es.json +++ b/homeassistant/components/homematicip_cloud/translations/es.json @@ -21,7 +21,7 @@ "title": "Elegir punto de acceso HomematicIP" }, "link": { - "description": "Pulsa el bot\u00f3n azul en el punto de acceso y el bot\u00f3n de env\u00edo para registrar HomematicIP en Home Assistant.\n\n![Ubicaci\u00f3n del bot\u00f3n en el puente](/static/images/config_flows/config_homematicip_cloud.png)", + "description": "Pulsa el bot\u00f3n azul en el punto de acceso y el bot\u00f3n de env\u00edo para registrar HomematicIP en Home Assistant.\n\n![Ubicaci\u00f3n del bot\u00f3n en la pasarela](/static/images/config_flows/config_homematicip_cloud.png)", "title": "Enlazar punto de acceso" } } diff --git a/homeassistant/components/huawei_lte/translations/es-419.json b/homeassistant/components/huawei_lte/translations/es-419.json new file mode 100644 index 00000000000..a00f805c9fa --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/es-419.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "Este dispositivo ya ha sido configurado", + "already_in_progress": "Este dispositivo ya est\u00e1 siendo configurado", + "not_huawei_lte": "No es un dispositivo Huawei LTE" + }, + "error": { + "connection_failed": "La conexi\u00f3n fall\u00f3", + "connection_timeout": "El tiempo de conexi\u00f3n expir\u00f3", + "incorrect_password": "Contrase\u00f1a incorrecta", + "incorrect_username": "Nombre de usuario incorrecto", + "incorrect_username_or_password": "Nombre de usuario o contrase\u00f1a incorrecta", + "invalid_url": "URL invalida", + "login_attempts_exceeded": "Se han excedido los intentos de inicio de sesi\u00f3n m\u00e1ximos. Vuelva a intentarlo m\u00e1s tarde", + "response_error": "Error desconocido del dispositivo", + "unknown_connection_error": "Error desconocido al conectarse al dispositivo" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "url": "URL", + "username": "Nombre de usuario" + }, + "description": "Ingrese los detalles de acceso del dispositivo. Especificar nombre de usuario y contrase\u00f1a es opcional, pero habilita el soporte para m\u00e1s funciones de integraci\u00f3n. Por otro lado, el uso de una conexi\u00f3n autorizada puede causar problemas para acceder a la interfaz web del dispositivo desde fuera de Home Assistant mientras la integraci\u00f3n est\u00e1 activa y viceversa.", + "title": "Configurar Huawei LTE" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "name": "Nombre del servicio de notificaci\u00f3n (el cambio requiere reiniciar)", + "recipient": "Destinatarios de notificaciones por SMS", + "track_new_devices": "Rastrear nuevos dispositivos" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hue/translations/es-419.json b/homeassistant/components/hue/translations/es-419.json index 8b2b90456bd..ec81c24e2cb 100644 --- a/homeassistant/components/hue/translations/es-419.json +++ b/homeassistant/components/hue/translations/es-419.json @@ -3,6 +3,7 @@ "abort": { "all_configured": "Todos los puentes Philips Hue ya est\u00e1n configurados", "already_configured": "El puente ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n para el puente ya est\u00e1 en progreso.", "cannot_connect": "No se puede conectar al puente", "discover_timeout": "Incapaz de descubrir puentes Hue", "no_bridges": "No se descubrieron puentes Philips Hue", @@ -17,8 +18,34 @@ "init": { "data": { "host": "Host" - } + }, + "title": "Seleccione el puente Hue" + }, + "link": { + "description": "Presione el bot\u00f3n en el puente para registrar Philips Hue con Home Assistant. \n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)", + "title": "Enlazar Hub" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primer bot\u00f3n", + "button_2": "Segundo bot\u00f3n", + "button_3": "Tercer bot\u00f3n", + "button_4": "Cuarto bot\u00f3n", + "dim_down": "Bajar la intensidad", + "dim_up": "Aumentar intensidad", + "double_buttons_1_3": "Botones primero y tercero", + "double_buttons_2_4": "Botones segundo y cuarto", + "turn_off": "Apagar", + "turn_on": "Encender" + }, + "trigger_type": { + "remote_button_long_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\" despu\u00e9s de una pulsaci\u00f3n prolongada", + "remote_button_short_press": "Se presion\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_button_short_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_double_button_long_press": "Ambos \"{subtype}\" sueltos despu\u00e9s de una pulsaci\u00f3n prolongada", + "remote_double_button_short_press": "Ambos \"{subtype}\" sueltos" + } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/es.json b/homeassistant/components/hue/translations/es.json index ddaad5c424a..f4762560485 100644 --- a/homeassistant/components/hue/translations/es.json +++ b/homeassistant/components/hue/translations/es.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "all_configured": "Todos los puentes Philips Hue ya est\u00e1n configurados", - "already_configured": "El puente ya esta configurado", - "already_in_progress": "El flujo de configuraci\u00f3n para el puente ya est\u00e1 en curso.", - "cannot_connect": "No se puede conectar al puente", - "discover_timeout": "No se han descubierto puentes Philips Hue", - "no_bridges": "No se han descubierto puentes Philips Hue.", - "not_hue_bridge": "No es un puente Hue", + "all_configured": "Ya se han configurado todas las pasarelas Philips Hue", + "already_configured": "La pasarela ya esta configurada", + "already_in_progress": "La configuraci\u00f3n del flujo para la pasarela ya est\u00e1 en curso.", + "cannot_connect": "No se puede conectar a la pasarela", + "discover_timeout": "Imposible encontrar pasarelas Philips Hue", + "no_bridges": "No se han encontrado pasarelas Philips Hue.", + "not_hue_bridge": "No es una pasarela Hue", "unknown": "Se produjo un error desconocido" }, "error": { @@ -19,10 +19,10 @@ "data": { "host": "Host" }, - "title": "Elige el puente de Hue" + "title": "Elige la pasarela Hue" }, "link": { - "description": "Presione el bot\u00f3n en el puente para registrar Philips Hue con Home Assistant. \n\n![Ubicaci\u00f3n del bot\u00f3n en el puente](/static/images/config_philips_hue.jpg)", + "description": "Presione el bot\u00f3n en la pasarela para registrar Philips Hue con Home Assistant. \n\n![Ubicaci\u00f3n del bot\u00f3n en la pasarela](/static/images/config_philips_hue.jpg)", "title": "Link Hub" } } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/es-419.json b/homeassistant/components/hunterdouglas_powerview/translations/es-419.json new file mode 100644 index 00000000000..c4a26d7d35d --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "link": { + "description": "\u00bfDesea configurar {name} ({host})?", + "title": "Conectar a Powerview Hub" + }, + "user": { + "data": { + "host": "Direcci\u00f3n IP" + }, + "title": "Conectar a Powerview Hub" + } + } + }, + "title": "Hunter Douglas PowerView" +} \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/es-419.json b/homeassistant/components/iaqualink/translations/es-419.json index 170c2851d08..9d43bfa7e57 100644 --- a/homeassistant/components/iaqualink/translations/es-419.json +++ b/homeassistant/components/iaqualink/translations/es-419.json @@ -1,12 +1,19 @@ { "config": { + "abort": { + "already_setup": "Solo puede configurar una \u00fanica conexi\u00f3n iAqualink." + }, + "error": { + "connection_failure": "No se puede conectar a iAqualink. Verifice su nombre de usuario y contrase\u00f1a." + }, "step": { "user": { "data": { "password": "Contrase\u00f1a", "username": "Nombre de usuario / direcci\u00f3n de correo electr\u00f3nico" }, - "description": "Por favor, Ingrese el nombre de usuario y la contrase\u00f1a para su cuenta de iAqualink." + "description": "Por favor, Ingrese el nombre de usuario y la contrase\u00f1a para su cuenta de iAqualink.", + "title": "Conectar a iAqualink" } } } diff --git a/homeassistant/components/icloud/translations/es-419.json b/homeassistant/components/icloud/translations/es-419.json new file mode 100644 index 00000000000..90fca1454cf --- /dev/null +++ b/homeassistant/components/icloud/translations/es-419.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya ha sido configurada", + "no_device": "Ninguno de sus dispositivos tiene activado \"Buscar mi iPhone\"" + }, + "error": { + "login": "Error de inicio de sesi\u00f3n: compruebe su correo electr\u00f3nico y contrase\u00f1a", + "send_verification_code": "Error al enviar el c\u00f3digo de verificaci\u00f3n", + "validate_verification_code": "No se pudo verificar su c\u00f3digo de verificaci\u00f3n, elija un dispositivo de confianza y comience la verificaci\u00f3n nuevamente" + }, + "step": { + "trusted_device": { + "data": { + "trusted_device": "Dispositivo de confianza" + }, + "description": "Selecciona tu dispositivo de confianza", + "title": "Dispositivo de confianza iCloud" + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Correo electr\u00f3nico", + "with_family": "Con la familia" + }, + "description": "Ingrese sus credenciales", + "title": "Credenciales de iCloud" + }, + "verification_code": { + "data": { + "verification_code": "C\u00f3digo de verificaci\u00f3n" + }, + "description": "Ingrese el c\u00f3digo de verificaci\u00f3n que acaba de recibir de iCloud", + "title": "C\u00f3digo de verificaci\u00f3n de iCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/es-419.json b/homeassistant/components/ifttt/translations/es-419.json index 097ecad5b79..4b648d64279 100644 --- a/homeassistant/components/ifttt/translations/es-419.json +++ b/homeassistant/components/ifttt/translations/es-419.json @@ -9,7 +9,8 @@ }, "step": { "user": { - "description": "\u00bfEst\u00e1s seguro de que quieres configurar IFTTT?" + "description": "\u00bfEst\u00e1s seguro de que quieres configurar IFTTT?", + "title": "Configurar el Applet de webhook IFTTT" } } } diff --git a/homeassistant/components/ios/translations/no.json b/homeassistant/components/ios/translations/no.json index 5645bd91e2a..826df14b1e3 100644 --- a/homeassistant/components/ios/translations/no.json +++ b/homeassistant/components/ios/translations/no.json @@ -5,7 +5,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 konfigurere Home Assistant iOS-komponenten?", + "description": "\u00d8nsker du \u00e5 sette opp Home Assistant iOS-komponenten?", "title": "Home Assistant iOS" } } diff --git a/homeassistant/components/ipma/translations/es-419.json b/homeassistant/components/ipma/translations/es-419.json index 7b319c66a49..a3f83c150e7 100644 --- a/homeassistant/components/ipma/translations/es-419.json +++ b/homeassistant/components/ipma/translations/es-419.json @@ -8,6 +8,7 @@ "data": { "latitude": "Latitud", "longitude": "Longitud", + "mode": "Modo", "name": "Nombre" }, "description": "Instituto Portugu\u00eas do Mar e Atmosfera", diff --git a/homeassistant/components/ipp/translations/es-419.json b/homeassistant/components/ipp/translations/es-419.json new file mode 100644 index 00000000000..eb9c21eb28c --- /dev/null +++ b/homeassistant/components/ipp/translations/es-419.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "Esta impresora ya est\u00e1 configurada.", + "connection_error": "No se pudo conectar a la impresora.", + "connection_upgrade": "No se pudo conectar a la impresora debido a que se requiere una actualizaci\u00f3n de la conexi\u00f3n.", + "ipp_error": "Error de IPP encontrado.", + "ipp_version_error": "La versi\u00f3n IPP no es compatible con la impresora.", + "parse_error": "Error al analizar la respuesta de la impresora." + }, + "error": { + "connection_error": "No se pudo conectar a la impresora.", + "connection_upgrade": "No se pudo conectar a la impresora. Intente nuevamente con la opci\u00f3n SSL/TLS marcada." + }, + "flow_title": "Impresora: {name}", + "step": { + "user": { + "data": { + "base_path": "Ruta relativa a la impresora", + "host": "Host o direcci\u00f3n IP", + "port": "Puerto", + "ssl": "La impresora admite la comunicaci\u00f3n a trav\u00e9s de SSL/TLS", + "verify_ssl": "La impresora usa un certificado SSL adecuado" + }, + "description": "Configure su impresora a trav\u00e9s del Protocolo de impresi\u00f3n de Internet (IPP) para integrarse con Home Assistant.", + "title": "Enlace su impresora" + }, + "zeroconf_confirm": { + "description": "\u00bfDesea agregar la impresora llamada `{name}` a Home Assistant?", + "title": "Impresora descubierta" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/no.json b/homeassistant/components/ipp/translations/no.json index 10ab960272b..fa5e4c86a89 100644 --- a/homeassistant/components/ipp/translations/no.json +++ b/homeassistant/components/ipp/translations/no.json @@ -22,7 +22,7 @@ "ssl": "Skriveren st\u00f8tter kommunikasjon over SSL/TLS", "verify_ssl": "Skriveren bruker et riktig SSL-sertifikat" }, - "description": "Konfigurer skriveren din via Internet Printing Protocol (IPP) for \u00e5 integrere med Home Assistant.", + "description": "Sett opp skriveren din via Internet Printing Protocol (IPP) for \u00e5 integrere med Home Assistant.", "title": "Koble til skriveren din" }, "zeroconf_confirm": { diff --git a/homeassistant/components/iqvia/translations/es-419.json b/homeassistant/components/iqvia/translations/es-419.json index b107e1bb696..21b5273bfba 100644 --- a/homeassistant/components/iqvia/translations/es-419.json +++ b/homeassistant/components/iqvia/translations/es-419.json @@ -1,13 +1,16 @@ { "config": { "error": { + "identifier_exists": "C\u00f3digo postal ya registrado", "invalid_zip_code": "El c\u00f3digo postal no es v\u00e1lido" }, "step": { "user": { "data": { "zip_code": "C\u00f3digo postal" - } + }, + "description": "Complete su c\u00f3digo postal de EE. UU. o Canad\u00e1.", + "title": "IQVIA" } } } diff --git a/homeassistant/components/islamic_prayer_times/translations/es-419.json b/homeassistant/components/islamic_prayer_times/translations/es-419.json new file mode 100644 index 00000000000..2a5105a4054 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/es-419.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Solo una instancia es necesaria." + }, + "step": { + "user": { + "description": "\u00bfDesea establecer tiempos de oraci\u00f3n isl\u00e1mica?", + "title": "Establecer tiempos de oraci\u00f3n isl\u00e1mica" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "calculation_method": "M\u00e9todo de c\u00e1lculo de la oraci\u00f3n" + } + } + } + }, + "title": "Tiempos de oraci\u00f3n isl\u00e1mica" +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/es-419.json b/homeassistant/components/izone/translations/es-419.json new file mode 100644 index 00000000000..b645c427e3e --- /dev/null +++ b/homeassistant/components/izone/translations/es-419.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "No se encontraron dispositivos iZone en la red.", + "single_instance_allowed": "Solo es necesaria una \u00fanica configuraci\u00f3n de iZone." + }, + "step": { + "confirm": { + "description": "\u00bfDesea configurar iZone?", + "title": "iZone" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/izone/translations/no.json b/homeassistant/components/izone/translations/no.json index 49c806aa564..854805948ee 100644 --- a/homeassistant/components/izone/translations/no.json +++ b/homeassistant/components/izone/translations/no.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere iZone?", + "description": "Vil du \u00e5 sette opp iZone?", "title": "" } } diff --git a/homeassistant/components/konnected/translations/es-419.json b/homeassistant/components/konnected/translations/es-419.json new file mode 100644 index 00000000000..a63ba501960 --- /dev/null +++ b/homeassistant/components/konnected/translations/es-419.json @@ -0,0 +1,108 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en progreso.", + "not_konn_panel": "No es un dispositivo Konnected.io reconocido", + "unknown": "Se produjo un error desconocido" + }, + "error": { + "cannot_connect": "No se puede conectar a un Panel Konnected en {host}: {port}" + }, + "step": { + "confirm": { + "description": "Modelo: {model} \nID: {id} \nHost: {host} \nPuerto: {port} \n\nPuede configurar el comportamiento de IO y del panel en la configuraci\u00f3n del Panel de alarmas conectadas.", + "title": "Dispositivo Konnected listo" + }, + "import_confirm": { + "description": "Se ha descubierto un Panel de alarma conectado con ID {id} en configuration.yaml. Este flujo le permitir\u00e1 importarlo a una entrada de configuraci\u00f3n.", + "title": "Importar dispositivo Konnected" + }, + "user": { + "data": { + "host": "Direcci\u00f3n IP del dispositivo Konnected", + "port": "Puerto de dispositivo Konnected" + }, + "description": "Ingrese la informaci\u00f3n del host para su Panel Konnected.", + "title": "Descubrir el dispositivo Konnected" + } + } + }, + "options": { + "abort": { + "not_konn_panel": "No es un dispositivo Konnected.io reconocido" + }, + "error": { + "bad_host": "URL de host de API de sobrescritura no v\u00e1lida" + }, + "step": { + "options_binary": { + "data": { + "inverse": "Invertir el estado abierto/cerrado", + "name": "Nombre (opcional)", + "type": "Tipo de sensor binario" + }, + "description": "Seleccione las opciones para el sensor binario conectado a {zone}", + "title": "Configurar sensor binario" + }, + "options_digital": { + "data": { + "name": "Nombre (opcional)", + "poll_interval": "Intervalo de sondeo (minutos) (opcional)", + "type": "Tipo de sensor" + }, + "description": "Seleccione las opciones para el sensor digital conectado a {zone}", + "title": "Configurar 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": "SALIDA" + }, + "description": "Descubierto un {model} en {host} . Seleccione la configuraci\u00f3n base de cada E/S a continuaci\u00f3n: seg\u00fan la E/S, puede permitir sensores binarios (contactos de apertura/cierre), sensores digitales (dht y ds18b20) o salidas conmutables. Podr\u00e1 configurar opciones detalladas en los pr\u00f3ximos pasos.", + "title": "Configurar E/S" + }, + "options_io_ext": { + "data": { + "10": "Zona 10", + "11": "Zona 11", + "12": "Zona 12", + "8": "Zona 8", + "9": "Zona 9", + "alarm1": "ALARMA1", + "alarm2_out2": "SALIDA2/ALARMA2", + "out1": "SALIDA1" + }, + "description": "Seleccione la configuraci\u00f3n de las E/S restantes a continuaci\u00f3n. Podr\u00e1 configurar opciones detalladas en los pr\u00f3ximos pasos.", + "title": "Configurar E/S extendida" + }, + "options_misc": { + "data": { + "api_host": "Sobrescribir URL de host de API (opcional)", + "blink": "Parpadea el LED del panel cuando se env\u00eda un cambio de estado", + "override_api_host": "Sobrescribir la URL predeterminada del panel de host de la API de Home Assistant" + }, + "description": "Seleccione el comportamiento deseado para su panel", + "title": "Configurar Misc" + }, + "options_switch": { + "data": { + "activation": "Salida cuando est\u00e1 encendido", + "momentary": "Duraci\u00f3n del pulso (ms) (opcional)", + "more_states": "Configurar estados adicionales para esta zona", + "name": "Nombre (opcional)", + "pause": "Pausa entre pulsos (ms) (opcional)", + "repeat": "Tiempos de repetici\u00f3n (-1 = infinito) (opcional)" + }, + "description": "Seleccione las opciones de salida para {zone}: state {state}", + "title": "Configurar salida conmutable" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index 566c623c80c..0cc9cf06896 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -6,8 +6,12 @@ "not_konn_panel": "Non reconnu comme appareil Konnected.io", "unknown": "Une erreur inconnue s'est produite" }, + "error": { + "cannot_connect": "Impossible de se connecter \u00e0 Konnected Panel sur {host} : {port}" + }, "step": { "confirm": { + "description": "Model: {model} \n ID: {id} \n Host: {host} \n Port: {port} \n\nVous pouvez configurer le comportement des E/S et du panneau dans les param\u00e8tres de Konnected Alarm Panel.", "title": "Appareil Konnected pr\u00eat" }, "import_confirm": { @@ -15,12 +19,18 @@ }, "user": { "data": { - "host": "Adresse IP de l\u2019appareil Konnected" - } + "host": "Adresse IP de l\u2019appareil Konnected", + "port": "Port de l'appareil Konnected" + }, + "description": "Veuillez saisir les informations de l\u2019h\u00f4te de votre panneau Konnected.", + "title": "D\u00e9couverte d\u2019appareil Konnected" } } }, "options": { + "abort": { + "not_konn_panel": "Non reconnu comme appareil Konnected.io" + }, "error": { "one": "Vide", "other": "Vide" diff --git a/homeassistant/components/life360/translations/es-419.json b/homeassistant/components/life360/translations/es-419.json index 33d8d59d080..627f5b8f198 100644 --- a/homeassistant/components/life360/translations/es-419.json +++ b/homeassistant/components/life360/translations/es-419.json @@ -1,8 +1,12 @@ { "config": { "abort": { + "invalid_credentials": "Credenciales no v\u00e1lidas", "user_already_configured": "La cuenta ya ha sido configurada" }, + "create_entry": { + "default": "Para establecer opciones avanzadas, consulte [Documentaci\u00f3n de Life360] ({docs_url})." + }, "error": { "invalid_credentials": "Credenciales no v\u00e1lidas", "invalid_username": "Nombre de usuario inv\u00e1lido", @@ -15,6 +19,7 @@ "password": "Contrase\u00f1a", "username": "Nombre de usuario" }, + "description": "Para establecer opciones avanzadas, consulte [Documentaci\u00f3n de Life360] ( {docs_url} ). \n Es posible que desee hacer eso antes de agregar cuentas.", "title": "Informaci\u00f3n de la cuenta Life360" } } diff --git a/homeassistant/components/light/translations/es-419.json b/homeassistant/components/light/translations/es-419.json index a36bd06e27e..f8939b689cf 100644 --- a/homeassistant/components/light/translations/es-419.json +++ b/homeassistant/components/light/translations/es-419.json @@ -1,5 +1,13 @@ { "device_automation": { + "action_type": { + "brightness_decrease": "Disminuya el brillo de {entity_name}", + "brightness_increase": "Aumenta el brillo de {entity_name}", + "flash": "Destello {entity_name}", + "toggle": "Alternar {entity_name}", + "turn_off": "Desactivar {entity_name}", + "turn_on": "Activar {entity_name}" + }, "condition_type": { "is_off": "{entity_name} est\u00e1 apagada", "is_on": "{entity_name} est\u00e1 encendida" diff --git a/homeassistant/components/light/translations/no.json b/homeassistant/components/light/translations/no.json index b9bd83ba190..77ac84c4440 100644 --- a/homeassistant/components/light/translations/no.json +++ b/homeassistant/components/light/translations/no.json @@ -3,7 +3,7 @@ "action_type": { "brightness_decrease": "Reduser lysstyrken p\u00e5 {entity_name}", "brightness_increase": "\u00d8k lysstyrken p\u00e5 {entity_name}", - "flash": "", + "flash": "Flash {entity_name}", "toggle": "Veksle {entity_name}", "turn_off": "Sl\u00e5 av {entity_name}", "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" diff --git a/homeassistant/components/linky/translations/es-419.json b/homeassistant/components/linky/translations/es-419.json index ff559803e06..58e44695fc8 100644 --- a/homeassistant/components/linky/translations/es-419.json +++ b/homeassistant/components/linky/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "La cuenta ya ha sido configurada" + }, "error": { "access": "No se pudo acceder a Enedis.fr, compruebe su conexi\u00f3n a Internet.", "enedis": "Enedis.fr respondi\u00f3 con un error: vuelva a intentarlo m\u00e1s tarde (normalmente no entre las 11 p.m. y las 2 a.m.)", diff --git a/homeassistant/components/local_ip/translations/es-419.json b/homeassistant/components/local_ip/translations/es-419.json new file mode 100644 index 00000000000..ba120a1ce14 --- /dev/null +++ b/homeassistant/components/local_ip/translations/es-419.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de IP local." + }, + "step": { + "user": { + "data": { + "name": "Nombre del sensor" + }, + "title": "Direcci\u00f3n IP local" + } + } + }, + "title": "Direcci\u00f3n IP local" +} \ No newline at end of file diff --git a/homeassistant/components/lock/translations/es-419.json b/homeassistant/components/lock/translations/es-419.json index ff9210f96fd..09b0932a014 100644 --- a/homeassistant/components/lock/translations/es-419.json +++ b/homeassistant/components/lock/translations/es-419.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "lock": "Bloquear {entity_name}", + "open": "Abrir {entity_name}", + "unlock": "Desbloquear {entity_name}" + }, + "condition_type": { + "is_locked": "{entity_name} est\u00e1 bloqueado", + "is_unlocked": "{entity_name} est\u00e1 desbloqueado" + }, + "trigger_type": { + "locked": "{entity_name} bloqueado", + "unlocked": "{entity_name} desbloqueado" + } + }, "state": { "_": { "locked": "Cerrado", diff --git a/homeassistant/components/logi_circle/translations/es-419.json b/homeassistant/components/logi_circle/translations/es-419.json index 8cc68833875..4f512189b32 100644 --- a/homeassistant/components/logi_circle/translations/es-419.json +++ b/homeassistant/components/logi_circle/translations/es-419.json @@ -3,22 +3,27 @@ "abort": { "already_setup": "Solo puede configurar una sola cuenta de Logi Circle.", "external_error": "Se produjo una excepci\u00f3n de otro flujo.", - "external_setup": "Logi Circle se configur\u00f3 correctamente desde otro flujo." + "external_setup": "Logi Circle se configur\u00f3 correctamente desde otro flujo.", + "no_flows": "Debe configurar Logi Circle antes de poder autenticarse con \u00e9l. [Lea las instrucciones] (https://www.home-assistant.io/components/logi_circle/)." }, "create_entry": { "default": "Autenticado con \u00e9xito con Logi Circle." }, "error": { - "auth_error": "Autorizaci\u00f3n de API fallida." + "auth_error": "Autorizaci\u00f3n de API fallida.", + "auth_timeout": "La autorizaci\u00f3n agot\u00f3 el tiempo de espera al solicitar el token de acceso.", + "follow_link": "Siga el enlace y autent\u00edquese antes de presionar Enviar." }, "step": { "auth": { + "description": "Siga el enlace a continuaci\u00f3n y Aceptar acceda a su cuenta de Logi Circle, luego regrese y presione Enviar continuaci\u00f3n. \n\n[Enlace] ({authorization_url})", "title": "Autenticar con Logi Circle" }, "user": { "data": { "flow_impl": "Proveedor" }, + "description": "Elija a trav\u00e9s del proveedor de autenticaci\u00f3n que desea autenticar con Logi Circle.", "title": "Proveedor de autenticaci\u00f3n" } } diff --git a/homeassistant/components/lutron_caseta/translations/es-419.json b/homeassistant/components/lutron_caseta/translations/es-419.json new file mode 100644 index 00000000000..970d722fe4c --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/es-419.json @@ -0,0 +1,3 @@ +{ + "title": "Lutron Cas\u00e9ta" +} \ No newline at end of file diff --git a/homeassistant/components/media_player/translations/es-419.json b/homeassistant/components/media_player/translations/es-419.json index 667d4af550e..518ca4453f0 100644 --- a/homeassistant/components/media_player/translations/es-419.json +++ b/homeassistant/components/media_player/translations/es-419.json @@ -1,4 +1,13 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} est\u00e1 inactivo", + "is_off": "{entity_name} est\u00e1 apagado", + "is_on": "{entity_name} est\u00e1 encendido", + "is_paused": "{entity_name} est\u00e1 en pausa", + "is_playing": "{entity_name} est\u00e1 reproduciendo" + } + }, "state": { "_": { "idle": "Inactivo", diff --git a/homeassistant/components/melcloud/translations/es-419.json b/homeassistant/components/melcloud/translations/es-419.json new file mode 100644 index 00000000000..0ee2674c8d9 --- /dev/null +++ b/homeassistant/components/melcloud/translations/es-419.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Integraci\u00f3n de MELCloud ya configurada para este correo electr\u00f3nico. El token de acceso se ha actualizado." + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a de MELCloud.", + "username": "Correo electr\u00f3nico utilizado para iniciar sesi\u00f3n en MELCloud." + }, + "description": "Con\u00e9ctese usando su cuenta MELCloud.", + "title": "Conectar con MELCloud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/fr.json b/homeassistant/components/melcloud/translations/fr.json index 0385113787c..a567058297e 100644 --- a/homeassistant/components/melcloud/translations/fr.json +++ b/homeassistant/components/melcloud/translations/fr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "L'int\u00e9gration MELCloud est d\u00e9j\u00e0 configur\u00e9e pour cet e-mail. Le jeton d'acc\u00e8s a \u00e9t\u00e9 actualis\u00e9." + }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", "invalid_auth": "Authentification non valide", diff --git a/homeassistant/components/meteo_france/translations/es-419.json b/homeassistant/components/meteo_france/translations/es-419.json new file mode 100644 index 00000000000..471b965a824 --- /dev/null +++ b/homeassistant/components/meteo_france/translations/es-419.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "La ciudad ya est\u00e1 configurada", + "unknown": "Error desconocido: vuelva a intentarlo m\u00e1s tarde" + }, + "step": { + "user": { + "data": { + "city": "Ciudad" + }, + "description": "Ingrese el c\u00f3digo postal (solo para Francia, recomendado) o el nombre de la ciudad", + "title": "M\u00e9t\u00e9o-Francia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/es-419.json b/homeassistant/components/mikrotik/translations/es-419.json new file mode 100644 index 00000000000..1b464bed393 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/es-419.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "already_configured": "Mikrotik ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Conexi\u00f3n fallida", + "name_exists": "El nombre existe", + "wrong_credentials": "Credenciales incorrectas" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nombre", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Nombre de usuario", + "verify_ssl": "Usar SSL" + }, + "title": "Configurar el Router Mikrotik" + } + } + }, + "options": { + "step": { + "device_tracker": { + "data": { + "arp_ping": "Habilitar ping ARP", + "detection_time": "Considere el intervalo de inicio", + "force_dhcp": "Escaneo forzado utilizando DHCP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/no.json b/homeassistant/components/mikrotik/translations/no.json index 6fa745a8b57..1e528fa4986 100644 --- a/homeassistant/components/mikrotik/translations/no.json +++ b/homeassistant/components/mikrotik/translations/no.json @@ -18,7 +18,7 @@ "username": "Brukernavn", "verify_ssl": "Bruk ssl" }, - "title": "Konfigurere Mikrotik-ruter" + "title": "Sett opp Mikrotik-ruter" } } }, diff --git a/homeassistant/components/minecraft_server/translations/es-419.json b/homeassistant/components/minecraft_server/translations/es-419.json new file mode 100644 index 00000000000..46a3ee9a029 --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/es-419.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El host ya est\u00e1 configurado." + }, + "error": { + "cannot_connect": "Error al conectar con el servidor. Verifique el host y el puerto e intente nuevamente. Tambi\u00e9n aseg\u00farese de que est\u00e9 ejecutando al menos Minecraft versi\u00f3n 1.7 en su servidor.", + "invalid_ip": "La direcci\u00f3n IP no es v\u00e1lida (no se pudo determinar la direcci\u00f3n MAC). Por favor corr\u00edjalo e intente nuevamente.", + "invalid_port": "El puerto debe estar en el rango de 1024 a 65535. Corr\u00edjalo e int\u00e9ntelo de nuevo." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nombre" + }, + "description": "Configure su instancia de Minecraft Server para permitir el monitoreo.", + "title": "Vincule su servidor Minecraft" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/fr.json b/homeassistant/components/minecraft_server/translations/fr.json index f2bb4dfa3de..44bd8230b4a 100644 --- a/homeassistant/components/minecraft_server/translations/fr.json +++ b/homeassistant/components/minecraft_server/translations/fr.json @@ -3,12 +3,18 @@ "abort": { "already_configured": "L'h\u00f4te est d\u00e9j\u00e0 configur\u00e9." }, + "error": { + "cannot_connect": "\u00c9chec de connexion au serveur. Veuillez v\u00e9rifier l'h\u00f4te et le port et r\u00e9essayer. Assurez-vous \u00e9galement que vous ex\u00e9cutez au moins Minecraft version 1.7 sur votre serveur.", + "invalid_ip": "L'adresse IP n'est pas valide (l'adresse MAC n'a pas pu \u00eatre d\u00e9termin\u00e9e). Veuillez le corriger et r\u00e9essayer.", + "invalid_port": "Le port doit \u00eatre compris entre 1024 et 65535. Veuillez le corriger et r\u00e9essayer." + }, "step": { "user": { "data": { "host": "H\u00f4te", "name": "Nom" }, + "description": "Configurez votre instance Minecraft Server pour permettre la surveillance.", "title": "Reliez votre serveur Minecraft" } } diff --git a/homeassistant/components/minecraft_server/translations/no.json b/homeassistant/components/minecraft_server/translations/no.json index bc3bb2955ad..4d2ecc6dbaa 100644 --- a/homeassistant/components/minecraft_server/translations/no.json +++ b/homeassistant/components/minecraft_server/translations/no.json @@ -14,7 +14,7 @@ "host": "Vert", "name": "Navn" }, - "description": "Konfigurer Minecraft Server-forekomsten slik at den kan overv\u00e5kes.", + "description": "Sett opp Minecraft Server-forekomsten slik at den kan overv\u00e5kes.", "title": "Link din Minecraft Server" } } diff --git a/homeassistant/components/mobile_app/translations/es-419.json b/homeassistant/components/mobile_app/translations/es-419.json index cb666a7d72d..0ad6fb613f9 100644 --- a/homeassistant/components/mobile_app/translations/es-419.json +++ b/homeassistant/components/mobile_app/translations/es-419.json @@ -5,6 +5,7 @@ }, "step": { "confirm": { + "description": "\u00bfDesea configurar el componente de la aplicaci\u00f3n m\u00f3vil?", "title": "Aplicaci\u00f3n movil" } } diff --git a/homeassistant/components/mobile_app/translations/no.json b/homeassistant/components/mobile_app/translations/no.json index 860d455d582..131e007751c 100644 --- a/homeassistant/components/mobile_app/translations/no.json +++ b/homeassistant/components/mobile_app/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "install_app": "\u00c5pne mobilappen for \u00e5 konfigurere integrasjonen med hjemmevirksomheten. Se [docs]({apps_url}) for en liste over kompatible apper." + "install_app": "\u00c5pne mobilappen for \u00e5 sette opp integrasjonen med Home Assistant. Se [dokumentasjon]({apps_url}) for en liste over kompatible apper." }, "step": { "confirm": { diff --git a/homeassistant/components/monoprice/translations/es-419.json b/homeassistant/components/monoprice/translations/es-419.json new file mode 100644 index 00000000000..66c7d5417ce --- /dev/null +++ b/homeassistant/components/monoprice/translations/es-419.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "port": "Puerto serial", + "source_1": "Nombre de la fuente #1", + "source_2": "Nombre de la fuente #2", + "source_3": "Nombre de la fuente #3", + "source_4": "Nombre de la fuente #4", + "source_5": "Nombre de la fuente #5", + "source_6": "Nombre de la fuente #6" + }, + "title": "Conectarse al dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "source_1": "Nombre de la fuente #1", + "source_2": "Nombre de la fuente #2", + "source_3": "Nombre de la fuente #3", + "source_4": "Nombre de la fuente #4", + "source_5": "Nombre de la fuente #5", + "source_6": "Nombre de la fuente #6" + }, + "title": "Configurar fuentes" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.es-419.json b/homeassistant/components/moon/translations/sensor.es-419.json index 107d3e46404..98242bed3ff 100644 --- a/homeassistant/components/moon/translations/sensor.es-419.json +++ b/homeassistant/components/moon/translations/sensor.es-419.json @@ -3,7 +3,12 @@ "moon__phase": { "first_quarter": "Cuarto creciente", "full_moon": "Luna llena", - "last_quarter": "Cuarto menguante" + "last_quarter": "Cuarto menguante", + "new_moon": "Luna nueva", + "waning_crescent": "Luna menguante", + "waning_gibbous": "Luna menguante gibosa", + "waxing_crescent": "Luna creciente", + "waxing_gibbous": "Luna creciente gibosa" } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/es-419.json b/homeassistant/components/mqtt/translations/es-419.json index af0bd912bc7..afb55aca1ce 100644 --- a/homeassistant/components/mqtt/translations/es-419.json +++ b/homeassistant/components/mqtt/translations/es-419.json @@ -26,5 +26,27 @@ "title": "MQTT Broker a trav\u00e9s del complemento Hass.io" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Primer bot\u00f3n", + "button_2": "Segundo bot\u00f3n", + "button_3": "Tercer bot\u00f3n", + "button_4": "Cuarto bot\u00f3n", + "button_5": "Quinto bot\u00f3n", + "button_6": "Sexto bot\u00f3n", + "turn_off": "Apagar", + "turn_on": "Encender" + }, + "trigger_type": { + "button_double_press": "\"{subtype}\" pulsado 2 veces", + "button_long_press": "\"{subtype}\" pulsado continuamente", + "button_long_release": "Se solt\u00f3 \"{subtype}\" despu\u00e9s de una pulsaci\u00f3n prolongada", + "button_quadruple_press": "\"{subtype}\" pulsado 4 veces", + "button_quintuple_press": "\"{subtype}\" pulsado 5 veces", + "button_short_press": "\"{subtype}\" presionado", + "button_short_release": "\"{subtype}\" soltado", + "button_triple_press": "\"{subtype}\" pulsado 3 veces" + } } } \ No newline at end of file diff --git a/homeassistant/components/myq/translations/es-419.json b/homeassistant/components/myq/translations/es-419.json new file mode 100644 index 00000000000..3d2f7a6ea71 --- /dev/null +++ b/homeassistant/components/myq/translations/es-419.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "MyQ ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Con\u00e9ctese a MyQ Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/es-419.json b/homeassistant/components/neato/translations/es-419.json new file mode 100644 index 00000000000..a6a684597f6 --- /dev/null +++ b/homeassistant/components/neato/translations/es-419.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Ya est\u00e1 configurado", + "invalid_credentials": "Credenciales no v\u00e1lidas" + }, + "create_entry": { + "default": "Consulte [Documentaci\u00f3n de Neato] ({docs_url})." + }, + "error": { + "invalid_credentials": "Credenciales no v\u00e1lidas", + "unexpected_error": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario", + "vendor": "Vendedor" + }, + "description": "Consulte [Documentaci\u00f3n de Neato] ({docs_url}).", + "title": "Informaci\u00f3n de cuenta de Neato" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nest/translations/es-419.json b/homeassistant/components/nest/translations/es-419.json index d816813855c..f7e19809f6b 100644 --- a/homeassistant/components/nest/translations/es-419.json +++ b/homeassistant/components/nest/translations/es-419.json @@ -3,10 +3,13 @@ "abort": { "already_setup": "Solo puedes configurar una sola cuenta Nest.", "authorize_url_fail": "Error desconocido al generar una URL de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", "no_flows": "Debe configurar Nest antes de poder autenticarse con \u00e9l. [Lea las instrucciones] (https://www.home-assistant.io/components/nest/)." }, "error": { + "internal_error": "C\u00f3digo de validaci\u00f3n de error interno", "invalid_code": "Codigo invalido", + "timeout": "Tiempo de espera agotado para validar el c\u00f3digo", "unknown": "Error desconocido al validar el c\u00f3digo" }, "step": { diff --git a/homeassistant/components/netatmo/translations/es-419.json b/homeassistant/components/netatmo/translations/es-419.json new file mode 100644 index 00000000000..d6f993e830e --- /dev/null +++ b/homeassistant/components/netatmo/translations/es-419.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_setup": "Solo puede configurar una cuenta de Netatmo.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "missing_configuration": "El componente Netatmo no est\u00e1 configurado. Por favor, siga la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado con \u00e9xito con Netatmo." + }, + "step": { + "pick_implementation": { + "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/es-419.json b/homeassistant/components/nexia/translations/es-419.json new file mode 100644 index 00000000000..e2f04c7d4b4 --- /dev/null +++ b/homeassistant/components/nexia/translations/es-419.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Esta casa de nexia ya est\u00e1 configurada" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Con\u00e9ctese a mynexia.com" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/es-419.json b/homeassistant/components/notion/translations/es-419.json index 6e0e7eb538f..a34d0356261 100644 --- a/homeassistant/components/notion/translations/es-419.json +++ b/homeassistant/components/notion/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Este nombre de usuario ya est\u00e1 en uso." + }, "error": { "invalid_credentials": "Nombre de usuario o contrase\u00f1a inv\u00e1lidos", "no_devices": "No se han encontrado dispositivos en la cuenta." diff --git a/homeassistant/components/nuheat/translations/es-419.json b/homeassistant/components/nuheat/translations/es-419.json new file mode 100644 index 00000000000..88e786c8b90 --- /dev/null +++ b/homeassistant/components/nuheat/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "El termostato ya est\u00e1 configurado." + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "invalid_thermostat": "El n\u00famero de serie del termostato no es v\u00e1lido.", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "serial_number": "N\u00famero de serie del termostato.", + "username": "Nombre de usuario" + }, + "description": "Deber\u00e1 obtener el n\u00famero de serie o ID num\u00e9rico de su termostato iniciando sesi\u00f3n en https://MyNuHeat.com y seleccionando su (s) termostato (s).", + "title": "Con\u00e9ctese a NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/es-419.json b/homeassistant/components/nut/translations/es-419.json new file mode 100644 index 00000000000..b33bc854b23 --- /dev/null +++ b/homeassistant/components/nut/translations/es-419.json @@ -0,0 +1,46 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "resources": { + "data": { + "resources": "Recursos" + }, + "title": "Seleccione los recursos para monitorear" + }, + "ups": { + "data": { + "alias": "Alias", + "resources": "Recursos" + }, + "title": "Seleccione el UPS para monitorear" + }, + "user": { + "data": { + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Nombre de usuario" + }, + "title": "Con\u00e9ctese al servidor NUT" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "resources": "Recursos", + "scan_interval": "Intervalo de escaneo (segundos)" + }, + "description": "Seleccione los Recursos del sensor." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/es-419.json b/homeassistant/components/nws/translations/es-419.json new file mode 100644 index 00000000000..a44b2899e3d --- /dev/null +++ b/homeassistant/components/nws/translations/es-419.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API (correo electr\u00f3nico)", + "latitude": "Latitud", + "longitude": "Longitud", + "station": "C\u00f3digo de estaci\u00f3n METAR" + }, + "description": "Si no se especifica un c\u00f3digo de estaci\u00f3n METAR, la latitud y la longitud se utilizar\u00e1n para encontrar la estaci\u00f3n m\u00e1s cercana.", + "title": "Con\u00e9ctese al Servicio Meteorol\u00f3gico Nacional" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/de.json b/homeassistant/components/onvif/translations/de.json new file mode 100644 index 00000000000..2a8dcb1b67b --- /dev/null +++ b/homeassistant/components/onvif/translations/de.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "Das ONVIF-Ger\u00e4t ist bereits konfiguriert.", + "already_in_progress": "Der Konfigurationsfluss f\u00fcr das ONVIF-Ger\u00e4t wird bereits ausgef\u00fchrt.", + "no_h264": "Es waren keine H264-Streams verf\u00fcgbar. \u00dcberpr\u00fcfen Sie die Profilkonfiguration auf Ihrem Ger\u00e4t.", + "no_mac": "Die eindeutige ID f\u00fcr das ONVIF-Ger\u00e4t konnte nicht konfiguriert werden.", + "onvif_error": "Fehler beim Einrichten des ONVIF-Ger\u00e4ts. \u00dcberpr\u00fcfen Sie die Protokolle auf weitere Informationen." + }, + "error": { + "connection_failed": "Es konnte keine Verbindung zum ONVIF-Dienst mit den angegebenen Anmeldeinformationen hergestellt werden." + }, + "step": { + "auth": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "title": "Konfigurieren Sie die Authentifizierung" + }, + "configure_profile": { + "data": { + "include": "Kameraentit\u00e4t erstellen" + }, + "description": "Kameraentit\u00e4t f\u00fcr {profile} mit {resolution} Aufl\u00f6sung erstellen?", + "title": "Profile konfigurieren" + }, + "device": { + "data": { + "host": "W\u00e4hlen Sie das erkannte ONVIF-Ger\u00e4t aus" + }, + "title": "W\u00e4hlen Sie ONVIF-Ger\u00e4t" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Konfigurieren Sie das ONVIF-Ger\u00e4t" + }, + "user": { + "description": "Wenn Sie auf Senden klicken, durchsuchen wir Ihr Netzwerk nach ONVIF-Ger\u00e4ten, die Profil S unterst\u00fctzen. \n\nEinige Hersteller haben begonnen, ONVIF standardm\u00e4\u00dfig zu deaktivieren. Stellen Sie sicher, dass ONVIF in der Konfiguration Ihrer Kamera aktiviert ist.", + "title": "ONVIF-Ger\u00e4tekonfiguration" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Zus\u00e4tzliche FFMPEG-Argumente", + "rtsp_transport": "RTSP-Transportmechanismus" + }, + "title": "ONVIF-Ger\u00e4teoptionen" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/es-419.json b/homeassistant/components/onvif/translations/es-419.json new file mode 100644 index 00000000000..823f1d15880 --- /dev/null +++ b/homeassistant/components/onvif/translations/es-419.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ONVIF ya est\u00e1 configurado.", + "already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ONVIF ya est\u00e1 en progreso.", + "no_h264": "No hab\u00eda transmisiones H264 disponibles. Verifique la configuraci\u00f3n del perfil en su dispositivo.", + "no_mac": "No se pudo configurar una identificaci\u00f3n \u00fanica para el dispositivo ONVIF.", + "onvif_error": "Error al configurar el dispositivo ONVIF. Consulte los registros para obtener m\u00e1s informaci\u00f3n." + }, + "error": { + "connection_failed": "No se pudo conectar al servicio ONVIF con las credenciales proporcionadas." + }, + "step": { + "auth": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Configurar autenticaci\u00f3n" + }, + "configure_profile": { + "data": { + "include": "Crear entidad de c\u00e1mara" + }, + "description": "\u00bfCrear entidad de c\u00e1mara para {profile} con una {resolution}?", + "title": "Configurar perfiles" + }, + "device": { + "data": { + "host": "Seleccione el dispositivo ONVIF descubierto" + }, + "title": "Seleccionar dispositivo ONVIF" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "title": "Configurar dispositivo ONVIF" + }, + "user": { + "description": "Al hacer clic en enviar, buscaremos en su red dispositivos ONVIF que admitan Profile S. \n\nAlgunos fabricantes han comenzado a deshabilitar ONVIF por defecto. Aseg\u00farese de que ONVIF est\u00e9 habilitado en la configuraci\u00f3n de su c\u00e1mara.", + "title": "Configuraci\u00f3n del dispositivo ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Argumentos adicionales de FFMPEG", + "rtsp_transport": "Mecanismo de transporte RTSP" + }, + "title": "Opciones de dispositivo ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/fr.json b/homeassistant/components/onvif/translations/fr.json index 38036b65517..b383aad3532 100644 --- a/homeassistant/components/onvif/translations/fr.json +++ b/homeassistant/components/onvif/translations/fr.json @@ -38,7 +38,7 @@ "title": "Configurer l\u2019appareil ONVIF" }, "user": { - "description": "En cliquant sur soumettre, nous rechercherons sur votre r\u00e9seau, des \u00e9quipements ONVIF qui supporte le Profile S.\n\nCertains constructeurs ont commenc\u00e9 \u00e0 d\u00e9sactiver ONvif par d\u00e9faut. Assurez vous que ONVIF est activ\u00e9 dans la configuration de votre cam\u00e9ra", + "description": "En cliquant sur soumettre, nous rechercherons sur votre r\u00e9seau, des \u00e9quipements ONVIF qui supporte le Profile S.\n\nCertains constructeurs ont commenc\u00e9 \u00e0 d\u00e9sactiver ONvif par d\u00e9faut. Assurez-vous qu\u2019ONVIF est activ\u00e9 dans la configuration de votre cam\u00e9ra", "title": "Configuration de l'appareil ONVIF" } } diff --git a/homeassistant/components/onvif/translations/it.json b/homeassistant/components/onvif/translations/it.json new file mode 100644 index 00000000000..689babb357c --- /dev/null +++ b/homeassistant/components/onvif/translations/it.json @@ -0,0 +1,58 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo ONVIF \u00e8 gi\u00e0 configurato.", + "already_in_progress": "Il flusso di configurazione per il dispositivo ONVIF \u00e8 gi\u00e0 in corso.", + "no_h264": "Non c'erano flussi H264 disponibili. Controllare la configurazione del profilo sul dispositivo.", + "no_mac": "Impossibile configurare l'ID univoco per il dispositivo ONVIF.", + "onvif_error": "Errore durante la configurazione del dispositivo ONVIF. Controllare i registri per ulteriori informazioni." + }, + "error": { + "connection_failed": "Impossibile connettersi al servizio ONVIF con le credenziali fornite." + }, + "step": { + "auth": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "title": "Configurare l'autenticazione" + }, + "configure_profile": { + "data": { + "include": "Crea entit\u00e0 telecamera" + }, + "description": "Creare un'entit\u00e0 telecamera per {profile} alla risoluzione {resolution}?", + "title": "Configurare i Profili" + }, + "device": { + "data": { + "host": "Seleziona il dispositivo ONVIF rilevato" + }, + "title": "Selezionare il dispositivo ONVIF" + }, + "manual_input": { + "data": { + "host": "Host", + "port": "Porta" + }, + "title": "Configurare il dispositivo ONVIF" + }, + "user": { + "description": "Facendo clic su Invia, cercheremo nella tua rete i dispositivi ONVIF che supportano il profilo S. \n\nAlcuni produttori hanno iniziato a disabilitare ONVIF per impostazione predefinita. Assicurati che ONVIF sia abilitato nella configurazione della tua telecamera.", + "title": "Configurazione del dispositivo ONVIF" + } + } + }, + "options": { + "step": { + "onvif_devices": { + "data": { + "extra_arguments": "Argomenti FFMPEG aggiuntivi", + "rtsp_transport": "Meccanismo di trasporto RTSP" + }, + "title": "Opzioni dispositivo ONVIF" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/opentherm_gw/translations/es-419.json b/homeassistant/components/opentherm_gw/translations/es-419.json new file mode 100644 index 00000000000..9338998d377 --- /dev/null +++ b/homeassistant/components/opentherm_gw/translations/es-419.json @@ -0,0 +1,31 @@ +{ + "config": { + "error": { + "already_configured": "Gateway ya configurado", + "id_exists": "La identificaci\u00f3n de la puerta ya existe", + "serial_error": "Error al conectarse al dispositivo", + "timeout": "Tiempo de intento de conexi\u00f3n agotado" + }, + "step": { + "init": { + "data": { + "device": "Ruta o URL", + "id": "Identificaci\u00f3n", + "name": "Nombre" + }, + "title": "OpenTherm Gateway" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "floor_temperature": "Temperatura del piso", + "precision": "Precisi\u00f3n" + }, + "description": "Opciones para OpenTherm Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/es-419.json b/homeassistant/components/panasonic_viera/translations/es-419.json new file mode 100644 index 00000000000..d396fc58856 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/es-419.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Esta televisi\u00f3n Panasonic Viera ya est\u00e1 configurada.", + "not_connected": "Se perdi\u00f3 la conexi\u00f3n remota con su televisi\u00f3n Panasonic Viera. Consulte los registros para obtener m\u00e1s informaci\u00f3n.", + "unknown": "Un error desconocido ocurri\u00f3. Consulte los registros para obtener m\u00e1s informaci\u00f3n." + }, + "error": { + "invalid_pin_code": "El c\u00f3digo PIN que ingres\u00f3 no es v\u00e1lido", + "not_connected": "No se pudo establecer una conexi\u00f3n remota con su televisi\u00f3n Panasonic Viera" + }, + "step": { + "pairing": { + "data": { + "pin": "PIN" + }, + "description": "Ingrese el PIN que se muestra en su televisi\u00f3n", + "title": "Emparejamiento" + }, + "user": { + "data": { + "host": "Direcci\u00f3n IP", + "name": "Nombre" + }, + "description": "Ingrese la direcci\u00f3n IP de su Panasonic Viera TV", + "title": "Configurar su televisi\u00f3n" + } + } + }, + "title": "Panasonic Viera" +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/es-419.json b/homeassistant/components/plex/translations/es-419.json index 8fa797aec89..72cd3deefbb 100644 --- a/homeassistant/components/plex/translations/es-419.json +++ b/homeassistant/components/plex/translations/es-419.json @@ -5,6 +5,7 @@ "already_configured": "Este servidor Plex ya est\u00e1 configurado", "already_in_progress": "Plex se est\u00e1 configurando", "invalid_import": "La configuraci\u00f3n importada no es v\u00e1lida", + "non-interactive": "Importaci\u00f3n no interactiva", "token_request_timeout": "Se agot\u00f3 el tiempo de espera para obtener el token", "unknown": "Fall\u00f3 por razones desconocidas" }, @@ -20,12 +21,21 @@ }, "description": "M\u00faltiples servidores disponibles, seleccione uno:", "title": "Seleccionar servidor Plex" + }, + "start_website_auth": { + "description": "Continuar autorizando en plex.tv.", + "title": "Conectar a servidor Plex" } } }, "options": { "step": { "plex_mp_settings": { + "data": { + "ignore_new_shared_users": "Ignorar nuevos usuarios administrados/compartidos", + "monitored_users": "Usuarios monitoreados", + "use_episode_art": "Usa el arte del episodio" + }, "description": "Opciones para reproductores multimedia Plex" } } diff --git a/homeassistant/components/point/translations/es-419.json b/homeassistant/components/point/translations/es-419.json index 2b6e51ba4a9..2b177e26825 100644 --- a/homeassistant/components/point/translations/es-419.json +++ b/homeassistant/components/point/translations/es-419.json @@ -3,7 +3,12 @@ "abort": { "already_setup": "Solo puede configurar una cuenta Point.", "authorize_url_fail": "Error desconocido al generar una URL de autorizaci\u00f3n.", - "external_setup": "Punto configurado con \u00e9xito desde otro flujo." + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "external_setup": "Punto configurado con \u00e9xito desde otro flujo.", + "no_flows": "Debe configurar Point antes de poder autenticarse con \u00e9l. [Lea las instrucciones] (https://www.home-assistant.io/components/point/)." + }, + "create_entry": { + "default": "Autenticado con \u00e9xito con Minut para sus dispositivos Point" }, "error": { "follow_link": "Por favor, siga el enlace y autent\u00edquese antes de presionar Enviar", @@ -11,7 +16,8 @@ }, "step": { "auth": { - "description": "Siga el enlace a continuaci\u00f3n y Aceptar acceso a su cuenta de Minut, luego vuelva y presione Enviar continuaci\u00f3n. \n\n [Enlace] ( {authorization_url} )" + "description": "Siga el enlace a continuaci\u00f3n y Aceptar acceso a su cuenta de Minut, luego vuelva y presione Enviar continuaci\u00f3n. \n\n [Enlace] ( {authorization_url} )", + "title": "Autenticaci\u00f3n Point" }, "user": { "data": { diff --git a/homeassistant/components/powerwall/translations/es-419.json b/homeassistant/components/powerwall/translations/es-419.json new file mode 100644 index 00000000000..fe8f6d061a9 --- /dev/null +++ b/homeassistant/components/powerwall/translations/es-419.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El powerwall ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado", + "wrong_version": "Su powerwall utiliza una versi\u00f3n de software que no es compatible. Considere actualizar o informar este problema para que pueda resolverse." + }, + "step": { + "user": { + "data": { + "ip_address": "Direcci\u00f3n IP" + }, + "title": "Conectar el powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/fr.json b/homeassistant/components/powerwall/translations/fr.json index d7e0c759bbf..3ddc6634557 100644 --- a/homeassistant/components/powerwall/translations/fr.json +++ b/homeassistant/components/powerwall/translations/fr.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", - "unknown": "Erreur inattendue" + "unknown": "Erreur inattendue", + "wrong_version": "Votre Powerwall utilise une version logicielle qui n'est pas prise en charge. Veuillez envisager de mettre \u00e0 niveau ou de signaler ce probl\u00e8me afin qu'il puisse \u00eatre r\u00e9solu." }, "step": { "user": { diff --git a/homeassistant/components/ps4/translations/es-419.json b/homeassistant/components/ps4/translations/es-419.json index eabbc340cc8..c718c4469aa 100644 --- a/homeassistant/components/ps4/translations/es-419.json +++ b/homeassistant/components/ps4/translations/es-419.json @@ -8,7 +8,9 @@ "port_997_bind_error": "No se pudo enlazar al puerto 997." }, "error": { + "credential_timeout": "Servicio de credenciales agotado. Presione enviar para reiniciar.", "login_failed": "No se ha podido emparejar con PlayStation 4. Verifique que el PIN sea correcto.", + "no_ipaddress": "Ingrese la direcci\u00f3n IP de la PlayStation 4 que desea configurar.", "not_ready": "PlayStation 4 no est\u00e1 encendida o conectada a la red." }, "step": { @@ -28,8 +30,10 @@ }, "mode": { "data": { + "ip_address": "Direcci\u00f3n IP (dejar en blanco si se utiliza el descubrimiento autom\u00e1tico).", "mode": "Modo de configuraci\u00f3n" }, + "description": "Seleccione el modo para la configuraci\u00f3n. El campo Direcci\u00f3n IP puede dejarse en blanco si selecciona Descubrimiento autom\u00e1tico, ya que los dispositivos se descubrir\u00e1n autom\u00e1ticamente.", "title": "Playstation 4" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json b/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json new file mode 100644 index 00000000000..ed6ab16c8b5 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/es-419.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "La integraci\u00f3n ya est\u00e1 configurada con un sensor existente con esa tarifa" + }, + "step": { + "user": { + "data": { + "name": "Nombre del sensor", + "tariff": "Tarifa contratada (1, 2 o 3 per\u00edodos)" + }, + "description": "Este sensor utiliza la API oficial para obtener [precios por hora de la electricidad (PVPC)] (https://www.esios.ree.es/es/pvpc) en Espa\u00f1a. \n Para obtener una explicaci\u00f3n m\u00e1s precisa, visite los [documentos de integraci\u00f3n] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSeleccione la tarifa contratada en funci\u00f3n de la cantidad de per\u00edodos de facturaci\u00f3n por d\u00eda: \n - 1 per\u00edodo: normal \n - 2 per\u00edodos: discriminaci\u00f3n (tarifa nocturna) \n - 3 per\u00edodos: coche el\u00e9ctrico (tarifa nocturna de 3 per\u00edodos)", + "title": "Selecci\u00f3n de tarifa" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/es-419.json b/homeassistant/components/rachio/translations/es-419.json new file mode 100644 index 00000000000..21186710e3d --- /dev/null +++ b/homeassistant/components/rachio/translations/es-419.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "api_key": "La clave API para la cuenta Rachio." + }, + "description": "Necesitar\u00e1 la clave API de https://app.rach.io/. Seleccione 'Configuraci\u00f3n de la cuenta y luego haga clic en' OBTENER CLAVE API '.", + "title": "Con\u00e9ctese a su dispositivo Rachio" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "manual_run_mins": "Por cu\u00e1nto tiempo, en minutos, encender una estaci\u00f3n cuando el interruptor est\u00e1 habilitado." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/es-419.json b/homeassistant/components/rainmachine/translations/es-419.json index 73da8c7c1d4..0767c509bf9 100644 --- a/homeassistant/components/rainmachine/translations/es-419.json +++ b/homeassistant/components/rainmachine/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Este controlador RainMachine ya est\u00e1 configurado." + }, "error": { "identifier_exists": "Cuenta ya registrada", "invalid_credentials": "Credenciales no v\u00e1lidas" diff --git a/homeassistant/components/ring/translations/es-419.json b/homeassistant/components/ring/translations/es-419.json new file mode 100644 index 00000000000..331769fbb4c --- /dev/null +++ b/homeassistant/components/ring/translations/es-419.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de dos factores" + }, + "title": "Autenticaci\u00f3n de dos factores" + }, + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Iniciar sesi\u00f3n con cuenta de Ring" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/es-419.json b/homeassistant/components/roku/translations/es-419.json new file mode 100644 index 00000000000..40a76670fe1 --- /dev/null +++ b/homeassistant/components/roku/translations/es-419.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo Roku ya est\u00e1 configurado", + "unknown": "Error inesperado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente" + }, + "flow_title": "Roku: {name}", + "step": { + "ssdp_confirm": { + "description": "\u00bfDesea configurar {name}?", + "title": "Roku" + }, + "user": { + "data": { + "host": "Host o direcci\u00f3n IP" + }, + "description": "Ingrese su informaci\u00f3n de Roku.", + "title": "Roku" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/es-419.json b/homeassistant/components/roomba/translations/es-419.json new file mode 100644 index 00000000000..d452c82b112 --- /dev/null +++ b/homeassistant/components/roomba/translations/es-419.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "certificate": "Certificado", + "continuous": "Continuo", + "delay": "Retraso", + "host": "Nombre de host o direcci\u00f3n IP", + "password": "Contrase\u00f1a" + }, + "description": "Actualmente recuperar el BLID y la contrase\u00f1a es un proceso manual. Siga los pasos descritos en la documentaci\u00f3n en: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Conectarse al dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Continuo", + "delay": "Retraso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/es-419.json b/homeassistant/components/samsungtv/translations/es-419.json new file mode 100644 index 00000000000..b35146e181e --- /dev/null +++ b/homeassistant/components/samsungtv/translations/es-419.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "Esta televisi\u00f3n Samsung ya est\u00e1 configurado.", + "already_in_progress": "La configuraci\u00f3n de la televisi\u00f3n Samsung ya est\u00e1 en progreso.", + "auth_missing": "Home Assistant no est\u00e1 autorizado para conectarse a esta televisi\u00f3n Samsung. Verifique la configuraci\u00f3n de su televisi\u00f3n para autorizar Home Assistant.", + "not_successful": "No se puede conectar a este dispositivo de TV Samsung.", + "not_supported": "Este dispositivo Samsung TV no es compatible actualmente." + }, + "flow_title": "Televisi\u00f3n Samsung: {model}", + "step": { + "confirm": { + "description": "\u00bfDesea configurar la televisi\u00f3n Samsung {model}? Si nunca conect\u00f3 Home Assistant antes, deber\u00eda ver una ventana emergente en su televisor pidiendo autorizaci\u00f3n. Las configuraciones manuales para este televisor se sobrescribir\u00e1n.", + "title": "Samsung TV" + }, + "user": { + "data": { + "host": "Host o direcci\u00f3n IP", + "name": "Nombre" + }, + "description": "Ingrese la informaci\u00f3n de su televisor Samsung. Si nunca conect\u00f3 Home Assistant antes, deber\u00eda ver una ventana emergente en su televisor pidiendo autorizaci\u00f3n.", + "title": "Samsung TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/no.json b/homeassistant/components/samsungtv/translations/no.json index 9240842ff97..490c716665e 100644 --- a/homeassistant/components/samsungtv/translations/no.json +++ b/homeassistant/components/samsungtv/translations/no.json @@ -7,7 +7,7 @@ "not_successful": "Kan ikke koble til denne Samsung TV-enheten.", "not_supported": "Denne Samsung TV-enhetene st\u00f8ttes forel\u00f8pig ikke." }, - "flow_title": "", + "flow_title": "Samsung TV: {model}", "step": { "confirm": { "description": "Vil du sette opp Samsung TV {model} ? Hvis du aldri har koblet til Home Assistant f\u00f8r, vil en popup p\u00e5 TVen be om godkjenning. Manuelle konfigurasjoner for denne TVen vil bli overskrevet.", diff --git a/homeassistant/components/season/translations/sensor.es-419.json b/homeassistant/components/season/translations/sensor.es-419.json index c0c3927357b..6837038ff3c 100644 --- a/homeassistant/components/season/translations/sensor.es-419.json +++ b/homeassistant/components/season/translations/sensor.es-419.json @@ -1,9 +1,16 @@ { "state": { + "season__season": { + "autumn": "Oto\u00f1o", + "spring": "Primavera", + "summer": "Verano", + "winter": "Invierno" + }, "season__season__": { "autumn": "Oto\u00f1o", "spring": "Primavera", - "summer": "Verano" + "summer": "Verano", + "winter": "Invierno" } } } \ No newline at end of file diff --git a/homeassistant/components/sense/translations/es-419.json b/homeassistant/components/sense/translations/es-419.json new file mode 100644 index 00000000000..7f643e13d63 --- /dev/null +++ b/homeassistant/components/sense/translations/es-419.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "email": "Direcci\u00f3n de correo electr\u00f3nico", + "password": "Contrase\u00f1a" + }, + "title": "Con\u00e9ctese a su monitor de energ\u00eda Sense" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/es-419.json b/homeassistant/components/sensor/translations/es-419.json index 6ed28293a90..e724fe3a106 100644 --- a/homeassistant/components/sensor/translations/es-419.json +++ b/homeassistant/components/sensor/translations/es-419.json @@ -1,4 +1,15 @@ { + "device_automation": { + "trigger_type": { + "battery_level": "{entity_name} cambios de nivel de bater\u00eda", + "humidity": "{entity_name} cambios de humedad", + "illuminance": "{entity_name} cambios de iluminancia", + "pressure": "{entity_name} cambios de presi\u00f3n", + "signal_strength": "{entity_name} cambios en la intensidad de la se\u00f1al", + "temperature": "{entity_name} cambios de temperatura", + "value": "{entity_name} cambios de valor" + } + }, "state": { "_": { "off": "", diff --git a/homeassistant/components/sentry/translations/es-419.json b/homeassistant/components/sentry/translations/es-419.json new file mode 100644 index 00000000000..7d207a3d5f2 --- /dev/null +++ b/homeassistant/components/sentry/translations/es-419.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Sentry ya est\u00e1 configurado" + }, + "error": { + "bad_dsn": "DSN inv\u00e1lido", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "description": "Ingrese su DSN Sentry", + "title": "Centinela" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/es-419.json b/homeassistant/components/shopping_list/translations/es-419.json new file mode 100644 index 00000000000..f9f1d78ea83 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/es-419.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "La lista de compras ya est\u00e1 configurada." + }, + "step": { + "user": { + "description": "\u00bfQuieres configurar la lista de compras?", + "title": "Lista de la compra" + } + } + }, + "title": "Lista de la compra" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/es-419.json b/homeassistant/components/simplisafe/translations/es-419.json index 135e9f843e9..6273cfa671b 100644 --- a/homeassistant/components/simplisafe/translations/es-419.json +++ b/homeassistant/components/simplisafe/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Esta cuenta SimpliSafe ya est\u00e1 en uso." + }, "error": { "identifier_exists": "Cuenta ya registrada", "invalid_credentials": "Credenciales no v\u00e1lidas" @@ -7,11 +10,22 @@ "step": { "user": { "data": { + "code": "C\u00f3digo (utilizado en la interfaz de usuario de Home Assistant)", "password": "Contrase\u00f1a", "username": "Direcci\u00f3n de correo electr\u00f3nico" }, "title": "Completa tu informaci\u00f3n" } } + }, + "options": { + "step": { + "init": { + "data": { + "code": "C\u00f3digo (utilizado en la interfaz de usuario de Home Assistant)" + }, + "title": "Configurar SimpliSafe" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/fr.json b/homeassistant/components/simplisafe/translations/fr.json index 4454c82c8f8..730b9b810d1 100644 --- a/homeassistant/components/simplisafe/translations/fr.json +++ b/homeassistant/components/simplisafe/translations/fr.json @@ -10,6 +10,7 @@ "step": { "user": { "data": { + "code": "Code (utilis\u00e9 dans l'interface Home Assistant)", "password": "Mot de passe", "username": "Adresse e-mail" }, diff --git a/homeassistant/components/smartthings/translations/es-419.json b/homeassistant/components/smartthings/translations/es-419.json index d5446773700..a07311b833e 100644 --- a/homeassistant/components/smartthings/translations/es-419.json +++ b/homeassistant/components/smartthings/translations/es-419.json @@ -1,13 +1,36 @@ { "config": { + "abort": { + "invalid_webhook_url": "Home Assistant no est\u00e1 configurado correctamente para recibir actualizaciones de SmartThings. La URL del webhook no es v\u00e1lida: \n > {webhook_url} \n\nActualice su configuraci\u00f3n seg\u00fan las [instrucciones] ({component_url}), reinicie Home Assistant e intente nuevamente.", + "no_available_locations": "No hay ubicaciones SmartThings disponibles para configurar en Home Assistant." + }, "error": { "app_setup_error": "No se puede configurar el SmartApp. Por favor, int\u00e9ntelo de nuevo.", + "token_forbidden": "El token no tiene los \u00e1mbitos de OAuth necesarios.", "token_invalid_format": "El token debe estar en formato UID/GUID", "token_unauthorized": "El token no es v\u00e1lido o ya no est\u00e1 autorizado.", "webhook_error": "SmartThings no pudo validar el endpoint configurado en `base_url`. Por favor, revise los requisitos de los componentes." }, "step": { + "authorize": { + "title": "Autorizar Home Assistant" + }, + "pat": { + "data": { + "access_token": "Token de acceso" + }, + "description": "Ingrese un SmartThings [Token de acceso personal] ({token_url}) que se ha creado seg\u00fan las [instrucciones] ({component_url}). Esto se usar\u00e1 para crear la integraci\u00f3n de Home Assistant dentro de su cuenta SmartThings.", + "title": "Ingresar token de acceso personal" + }, + "select_location": { + "data": { + "location_id": "Ubicaci\u00f3n" + }, + "description": "Seleccione la ubicaci\u00f3n de SmartThings que desea agregar a Home Assistant. Luego, abriremos una nueva ventana y le pediremos que inicie sesi\u00f3n y autorice la instalaci\u00f3n de la integraci\u00f3n de Home Assistant en la ubicaci\u00f3n seleccionada.", + "title": "Seleccionar ubicaci\u00f3n" + }, "user": { + "description": "SmartThings se configurar\u00e1 para enviar actualizaciones push a Home Assistant en: \n > {webhook_url} \n\nSi esto no es correcto, actualice su configuraci\u00f3n, reinicie Home Assistant e intente nuevamente.", "title": "Ingresar token de acceso personal" } } diff --git a/homeassistant/components/solaredge/translations/es-419.json b/homeassistant/components/solaredge/translations/es-419.json new file mode 100644 index 00000000000..7cbd7f1b5a1 --- /dev/null +++ b/homeassistant/components/solaredge/translations/es-419.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "site_exists": "Este site_id ya est\u00e1 configurado" + }, + "error": { + "site_exists": "Este site_id ya est\u00e1 configurado" + }, + "step": { + "user": { + "data": { + "api_key": "La clave API para este sitio", + "name": "El nombre de esta instalaci\u00f3n.", + "site_id": "La identificaci\u00f3n del sitio de SolarEdge" + }, + "title": "Definir los par\u00e1metros de API para esta instalaci\u00f3n." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/es-419.json b/homeassistant/components/solarlog/translations/es-419.json new file mode 100644 index 00000000000..9f17072f424 --- /dev/null +++ b/homeassistant/components/solarlog/translations/es-419.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar, verifique la direcci\u00f3n del host" + }, + "step": { + "user": { + "data": { + "host": "El nombre de host o la direcci\u00f3n IP de su dispositivo Solar-Log", + "name": "El prefijo que se utilizar\u00e1 para sus sensores de registro solar" + }, + "title": "Define tu conexi\u00f3n Solar-Log" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/soma/translations/es-419.json b/homeassistant/components/soma/translations/es-419.json new file mode 100644 index 00000000000..11c50fd5fb9 --- /dev/null +++ b/homeassistant/components/soma/translations/es-419.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_setup": "Solo puede configurar una cuenta Soma.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "connection_error": "No se pudo conectar a SOMA Connect.", + "missing_configuration": "El componente Soma no est\u00e1 configurado. Por favor, siga la documentaci\u00f3n.", + "result_error": "SOMA Connect respondi\u00f3 con estado de error." + }, + "create_entry": { + "default": "Autenticado con \u00e9xito con Soma." + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "description": "Ingrese la configuraci\u00f3n de conexi\u00f3n de su SOMA Connect.", + "title": "SOMA Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/es-419.json b/homeassistant/components/somfy/translations/es-419.json index ea066156c71..3667a72315b 100644 --- a/homeassistant/components/somfy/translations/es-419.json +++ b/homeassistant/components/somfy/translations/es-419.json @@ -1,3 +1,17 @@ { - "title": "Somfy" + "config": { + "abort": { + "already_setup": "Solo puede configurar una cuenta Somfy.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "missing_configuration": "El componente Somfy no est\u00e1 configurado. Por favor, siga la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado con \u00e9xito con Somfy." + }, + "step": { + "pick_implementation": { + "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" + } + } + } } \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/es-419.json b/homeassistant/components/spotify/translations/es-419.json new file mode 100644 index 00000000000..f9b956e47bc --- /dev/null +++ b/homeassistant/components/spotify/translations/es-419.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_setup": "Solo puede configurar una cuenta de Spotify.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "missing_configuration": "La integraci\u00f3n de Spotify no est\u00e1 configurada. Por favor, siga la documentaci\u00f3n." + }, + "create_entry": { + "default": "Autenticado correctamente con Spotify." + }, + "step": { + "pick_implementation": { + "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/es-419.json b/homeassistant/components/starline/translations/es-419.json new file mode 100644 index 00000000000..d6cdfa31b13 --- /dev/null +++ b/homeassistant/components/starline/translations/es-419.json @@ -0,0 +1,41 @@ +{ + "config": { + "error": { + "error_auth_app": "Identificaci\u00f3n de aplicaci\u00f3n incorrecta o secreto", + "error_auth_mfa": "C\u00f3digo incorrecto", + "error_auth_user": "Nombre de usuario o contrase\u00f1a incorrecta" + }, + "step": { + "auth_app": { + "data": { + "app_id": "ID de la aplicaci\u00f3n", + "app_secret": "Secreto" + }, + "description": "ID de la aplicaci\u00f3n y c\u00f3digo secreto de cuenta de desarrollador de StarLine ", + "title": "Credenciales de solicitud" + }, + "auth_captcha": { + "data": { + "captcha_code": "C\u00f3digo de imagen" + }, + "description": "{captcha_img}", + "title": "Captcha" + }, + "auth_mfa": { + "data": { + "mfa_code": "C\u00f3digo SMS" + }, + "description": "Ingrese el c\u00f3digo enviado al tel\u00e9fono {phone_number}", + "title": "Autorizaci\u00f3n de dos factores" + }, + "auth_user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "description": "Correo electr\u00f3nico y contrase\u00f1a de la cuenta StarLine", + "title": "Credenciales de usuario" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/switch/translations/es-419.json b/homeassistant/components/switch/translations/es-419.json index 83dc31ade83..7fb04127b15 100644 --- a/homeassistant/components/switch/translations/es-419.json +++ b/homeassistant/components/switch/translations/es-419.json @@ -1,6 +1,7 @@ { "device_automation": { "action_type": { + "toggle": "Alternar {entity_name}", "turn_off": "Desactivar {entity_name}", "turn_on": "Activar {entity_name}" }, diff --git a/homeassistant/components/synology_dsm/translations/es-419.json b/homeassistant/components/synology_dsm/translations/es-419.json new file mode 100644 index 00000000000..cad627e0dfb --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/es-419.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "already_configured": "Host ya configurado" + }, + "error": { + "connection": "Error de conexi\u00f3n: compruebe su host, puerto y ssl", + "login": "Error de inicio de sesi\u00f3n: compruebe su nombre de usuario y contrase\u00f1a", + "missing_data": "Datos faltantes: vuelva a intentarlo m\u00e1s tarde u otra configuraci\u00f3n", + "otp_failed": "La autenticaci\u00f3n de dos pasos fall\u00f3, vuelva a intentar con un nuevo c\u00f3digo de acceso", + "unknown": "Error desconocido: verifique los registros para obtener m\u00e1s detalles" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "C\u00f3digo" + }, + "title": "Synology DSM: autenticaci\u00f3n en dos pasos" + }, + "link": { + "data": { + "api_version": "Versi\u00f3n DSM", + "password": "Contrase\u00f1a", + "port": "Puerto (opcional)", + "ssl": "Utilice SSL/TLS para conectarse a su NAS", + "username": "Nombre de usuario" + }, + "description": "\u00bfDesea configurar {name} ({host})?", + "title": "Synology DSM" + }, + "user": { + "data": { + "api_version": "Versi\u00f3n DSM", + "host": "Host", + "password": "Contrase\u00f1a", + "port": "Puerto (opcional)", + "ssl": "Utilice SSL/TLS para conectarse a su NAS", + "username": "Nombre de usuario" + }, + "title": "Synology DSM" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index 7b2301768db..8c12e1b078a 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -10,7 +10,7 @@ "otp_failed": "To-trinns autentisering mislyktes. Pr\u00f8v p\u00e5 nytt med en ny passkode", "unknown": "Ukjent feil: sjekk loggene for \u00e5 f\u00e5 flere detaljer" }, - "flow_title": "", + "flow_title": "Synology DSM {name} ({host})", "step": { "2sa": { "data": { diff --git a/homeassistant/components/tado/translations/es-419.json b/homeassistant/components/tado/translations/es-419.json new file mode 100644 index 00000000000..3b9f1381eda --- /dev/null +++ b/homeassistant/components/tado/translations/es-419.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se pudo conectar, intente nuevamente", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida", + "no_homes": "No hay hogares vinculados a esta cuenta Tado.", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Con\u00e9ctese a su cuenta de Tado" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Habilitar el modo de fallback." + }, + "description": "El modo Fallback cambiar\u00e1 a Smart Schedule en el siguiente cambio de programaci\u00f3n despu\u00e9s de ajustar manualmente una zona.", + "title": "Ajusta las opciones de Tado." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/es-419.json b/homeassistant/components/tellduslive/translations/es-419.json index 36b358192be..71529c1f41d 100644 --- a/homeassistant/components/tellduslive/translations/es-419.json +++ b/homeassistant/components/tellduslive/translations/es-419.json @@ -3,6 +3,7 @@ "abort": { "already_setup": "TelldusLive ya est\u00e1 configurado", "authorize_url_fail": "Error desconocido al generar una URL de autorizaci\u00f3n.", + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", "unknown": "Se produjo un error desconocido" }, "error": { @@ -16,7 +17,8 @@ "user": { "data": { "host": "Host" - } + }, + "title": "Elegir punto final." } } } diff --git a/homeassistant/components/tesla/translations/es-419.json b/homeassistant/components/tesla/translations/es-419.json new file mode 100644 index 00000000000..d29077e688d --- /dev/null +++ b/homeassistant/components/tesla/translations/es-419.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "connection_error": "Error al conectar verifique la red y vuelva a intentar", + "identifier_exists": "correo electr\u00f3nico ya registrado", + "invalid_credentials": "Credenciales no v\u00e1lidas", + "unknown_error": "Error desconocido, informe la informaci\u00f3n del registro" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Direcci\u00f3n de correo electr\u00f3nico" + }, + "description": "Por favor ingrese su informaci\u00f3n.", + "title": "Tesla - Configuraci\u00f3n" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "Forzar a autom\u00f3viles despertar al inicio", + "scan_interval": "Segundos entre escaneos" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/toon/translations/es-419.json b/homeassistant/components/toon/translations/es-419.json index 6fa63e591a3..af39c29971d 100644 --- a/homeassistant/components/toon/translations/es-419.json +++ b/homeassistant/components/toon/translations/es-419.json @@ -1,7 +1,10 @@ { "config": { "abort": { + "client_id": "La identificaci\u00f3n del cliente de la configuraci\u00f3n no es v\u00e1lida.", + "client_secret": "El secreto del cliente de la configuraci\u00f3n no es v\u00e1lido.", "no_agreements": "Esta cuenta no tiene pantallas Toon.", + "no_app": "Debe configurar Toon antes de poder autenticarse con \u00e9l. [Lea las instrucciones] (https://www.home-assistant.io/components/toon/).", "unknown_auth_fail": "Ocurri\u00f3 un error inesperado, mientras se autenticaba." }, "error": { @@ -12,8 +15,10 @@ "authenticate": { "data": { "password": "Contrase\u00f1a", + "tenant": "Tenant", "username": "Nombre de usuario" }, + "description": "Autent\u00edquese con su cuenta de Eneco Toon (no con la cuenta de desarrollador).", "title": "Vincula tu cuenta de Toon" }, "display": { diff --git a/homeassistant/components/totalconnect/translations/es-419.json b/homeassistant/components/totalconnect/translations/es-419.json new file mode 100644 index 00000000000..421d2667099 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/es-419.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya ha sido configurada" + }, + "error": { + "login": "Error de inicio de sesi\u00f3n: compruebe su nombre de usuario y contrase\u00f1a" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Nombre de usuario" + }, + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/es-419.json b/homeassistant/components/traccar/translations/es-419.json index bfe62cc4e78..17f7560a464 100644 --- a/homeassistant/components/traccar/translations/es-419.json +++ b/homeassistant/components/traccar/translations/es-419.json @@ -3,6 +3,15 @@ "abort": { "not_internet_accessible": "Su instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes de Traccar.", "one_instance_allowed": "Solo una instancia es necesaria." + }, + "create_entry": { + "default": "Para enviar eventos a Home Assistant, deber\u00e1 configurar la funci\u00f3n webhook en Traccar. \n\n Use la siguiente URL: `{webhook_url}` \n\n Consulte [la documentaci\u00f3n] ({docs_url}) para obtener m\u00e1s detalles." + }, + "step": { + "user": { + "description": "\u00bfSeguro que desea configurar Traccar?", + "title": "Configurar Traccar" + } } } } \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/es.json b/homeassistant/components/tradfri/translations/es.json index 1c66cca87b6..cf1a1e81b5a 100644 --- a/homeassistant/components/tradfri/translations/es.json +++ b/homeassistant/components/tradfri/translations/es.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "El puente ya esta configurado", - "already_in_progress": "La configuraci\u00f3n del bridge ya est\u00e1 en marcha." + "already_configured": "La pasarela ya est\u00e1 configurada", + "already_in_progress": "La configuraci\u00f3n de la pasarela ya est\u00e1 en marcha." }, "error": { "cannot_connect": "No se puede conectar a la puerta de enlace.", diff --git a/homeassistant/components/transmission/translations/es-419.json b/homeassistant/components/transmission/translations/es-419.json index 6a01b3e25a1..002d03ee7d2 100644 --- a/homeassistant/components/transmission/translations/es-419.json +++ b/homeassistant/components/transmission/translations/es-419.json @@ -1,10 +1,33 @@ { + "config": { + "abort": { + "already_configured": "El host ya est\u00e1 configurado." + }, + "error": { + "cannot_connect": "No se puede conectar al host", + "name_exists": "El nombre ya existe", + "wrong_credentials": "Nombre de usuario o contrase\u00f1a incorrectos" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nombre", + "password": "Contrase\u00f1a", + "port": "Puerto", + "username": "Nombre de usuario" + }, + "title": "Configurar cliente de transmisi\u00f3n" + } + } + }, "options": { "step": { "init": { "data": { "scan_interval": "Frecuencia de actualizaci\u00f3n" - } + }, + "title": "Configurar opciones para la transmisi\u00f3n" } } } diff --git a/homeassistant/components/twentemilieu/translations/es-419.json b/homeassistant/components/twentemilieu/translations/es-419.json index 5cc3dc4b2c9..6f190ba503c 100644 --- a/homeassistant/components/twentemilieu/translations/es-419.json +++ b/homeassistant/components/twentemilieu/translations/es-419.json @@ -1,11 +1,20 @@ { "config": { + "abort": { + "address_exists": "Direcci\u00f3n ya configurada." + }, + "error": { + "connection_error": "Error al conectar.", + "invalid_address": "Direcci\u00f3n no encontrada en el \u00e1rea de servicio de Twente Milieu." + }, "step": { "user": { "data": { + "house_letter": "Carta de la casa/adicional", "house_number": "N\u00famero de casa", "post_code": "C\u00f3digo postal" }, + "description": "Configure Twente Milieu proporcionando informaci\u00f3n de recolecci\u00f3n de residuos en su direcci\u00f3n.", "title": "Twente Milieu" } } diff --git a/homeassistant/components/twentemilieu/translations/no.json b/homeassistant/components/twentemilieu/translations/no.json index d7d383ae371..a9d3c184495 100644 --- a/homeassistant/components/twentemilieu/translations/no.json +++ b/homeassistant/components/twentemilieu/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "address_exists": "Adressen er allerede konfigurert." + "address_exists": "Adressen er allerede satt opp." }, "error": { "connection_error": "Tilkobling mislyktes.", diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 0683f3da594..d7b92502f0b 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -51,6 +51,9 @@ "other": "andere" } }, + "simple_options": { + "description": "Konfigurieren Sie die UniFi-Integration" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Bandbreitennutzungssensoren f\u00fcr Netzwerkclients" diff --git a/homeassistant/components/unifi/translations/es-419.json b/homeassistant/components/unifi/translations/es-419.json index ac50051b6c8..7f92a0b45af 100644 --- a/homeassistant/components/unifi/translations/es-419.json +++ b/homeassistant/components/unifi/translations/es-419.json @@ -6,7 +6,8 @@ }, "error": { "faulty_credentials": "Credenciales de usuario incorrectas", - "service_unavailable": "No hay servicio disponible" + "service_unavailable": "No hay servicio disponible", + "unknown_client_mac": "Ning\u00fan cliente disponible en esa direcci\u00f3n MAC" }, "step": { "user": { @@ -21,5 +22,45 @@ "title": "Configurar el controlador UniFi" } } + }, + "options": { + "step": { + "client_control": { + "data": { + "block_client": "Acceso controlado a la red de clientes", + "new_client": "Agregar nuevo cliente para control de acceso a la red", + "poe_clients": "Permitir control POE de clientes" + }, + "description": "Configurar controles de cliente \n\nCree conmutadores para los n\u00fameros de serie para los que desea controlar el acceso a la red.", + "title": "Opciones UniFi 2/3" + }, + "device_tracker": { + "data": { + "detection_time": "Tiempo en segundos desde la \u00faltima vez que se vio hasta que se consider\u00f3", + "ignore_wired_bug": "Deshabilitar la l\u00f3gica de error con cable UniFi", + "ssid_filter": "Seleccione SSID para rastrear clientes inal\u00e1mbricos en", + "track_clients": "Rastree clientes de red", + "track_devices": "Dispositivos de red de seguimiento (dispositivos Ubiquiti)", + "track_wired_clients": "Incluir clientes de red cableada" + }, + "description": "Configurar el seguimiento del dispositivo", + "title": "Opciones UniFi 1/3" + }, + "simple_options": { + "data": { + "block_client": "Acceso controlado a la red de clientes", + "track_clients": "Rastree clientes en la red", + "track_devices": "Rastree dispositivos de red (dispositivos Ubiquiti)" + }, + "description": "Configurar la integraci\u00f3n de UniFi" + }, + "statistics_sensors": { + "data": { + "allow_bandwidth_sensors": "Sensores de uso de ancho de banda para clientes de red" + }, + "description": "Configurar sensores de estad\u00edsticas", + "title": "Opciones UniFi 3/3" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index cde18ca4029..af902565b92 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -52,6 +52,14 @@ "other": "altri" } }, + "simple_options": { + "data": { + "block_client": "Accesso alla rete dei client controllati", + "track_clients": "Traccia i client di rete", + "track_devices": "Traccia i dispositivi di rete (dispositivi Ubiquiti)" + }, + "description": "Configurare l'integrazione UniFi" + }, "statistics_sensors": { "data": { "allow_bandwidth_sensors": "Sensori di utilizzo della larghezza di banda per i client di rete" diff --git a/homeassistant/components/upnp/translations/no.json b/homeassistant/components/upnp/translations/no.json index 3004ab40ee7..75f052773fd 100644 --- a/homeassistant/components/upnp/translations/no.json +++ b/homeassistant/components/upnp/translations/no.json @@ -18,7 +18,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 konfigurere UPnP / IGD?", + "description": "\u00d8nsker du \u00e5 sette opp UPnP / IGD?", "title": "UPnP / IGD" }, "init": { diff --git a/homeassistant/components/vacuum/translations/es-419.json b/homeassistant/components/vacuum/translations/es-419.json index 39ed128de9d..59126d08a42 100644 --- a/homeassistant/components/vacuum/translations/es-419.json +++ b/homeassistant/components/vacuum/translations/es-419.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "clean": "Deje que {entity_name} limpie", + "dock": "Dejar que {entity_name} regrese al dock" + }, + "condition_type": { + "is_cleaning": "{entity_name} est\u00e1 limpiando", + "is_docked": "{entity_name} est\u00e1 acoplado" + }, + "trigger_type": { + "cleaning": "{entity_name} comenz\u00f3 a limpiar", + "docked": "{entity_name} acoplado" + } + }, "state": { "_": { "cleaning": "Limpiando", diff --git a/homeassistant/components/vera/translations/es-419.json b/homeassistant/components/vera/translations/es-419.json new file mode 100644 index 00000000000..243050b1544 --- /dev/null +++ b/homeassistant/components/vera/translations/es-419.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Un controlador ya est\u00e1 configurado.", + "cannot_connect": "No se pudo conectar al controlador con la URL {base_url}" + }, + "step": { + "user": { + "data": { + "exclude": "Identificadores de dispositivo de Vera para excluir de Home Assistant.", + "lights": "Vera cambia los identificadores del dispositivo para tratarlos como luces en Home Assistant.", + "vera_controller_url": "URL del controlador" + }, + "description": "Proporcione una URL del controlador Vera a continuaci\u00f3n. Deber\u00eda verse as\u00ed: http://192.168.1.161:3480.", + "title": "Configurar el controlador Vera" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "exclude": "Identificadores de dispositivo de Vera para excluir de Home Assistant.", + "lights": "Vera cambia los identificadores del dispositivo para tratarlos como luces en Home Assistant." + }, + "description": "Consulte la documentaci\u00f3n de vera para obtener detalles sobre los par\u00e1metros opcionales: https://www.home-assistant.io/integrations/vera/. Nota: Cualquier cambio aqu\u00ed necesitar\u00e1 reiniciar el servidor del asistente de inicio. Para borrar valores, proporcione un espacio.", + "title": "Opciones de controlador Vera" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/es-419.json b/homeassistant/components/vilfo/translations/es-419.json index 524b7e7b934..9c62c327235 100644 --- a/homeassistant/components/vilfo/translations/es-419.json +++ b/homeassistant/components/vilfo/translations/es-419.json @@ -1,7 +1,20 @@ { "config": { + "abort": { + "already_configured": "Este enrutador Vilfo ya est\u00e1 configurado." + }, + "error": { + "cannot_connect": "No se pudo conectar. Verifique la informaci\u00f3n que proporcion\u00f3 e intente nuevamente.", + "invalid_auth": "Autenticaci\u00f3n inv\u00e1lida Verifique el token de acceso e intente nuevamente.", + "unknown": "Se produjo un error inesperado al configurar la integraci\u00f3n." + }, "step": { "user": { + "data": { + "access_token": "Token de acceso para la API del enrutador Vilfo", + "host": "Nombre de host o IP del enrutador" + }, + "description": "Configure la integraci\u00f3n del enrutador Vilfo. Necesita su nombre de host/IP de Vilfo Router y un token de acceso API. Para obtener informaci\u00f3n adicional sobre esta integraci\u00f3n y c\u00f3mo obtener esos detalles, visite: https://www.home-assistant.io/integrations/vilfo", "title": "Conectar con el Router Vilfo" } } diff --git a/homeassistant/components/vilfo/translations/no.json b/homeassistant/components/vilfo/translations/no.json index 36c6e79989b..d5b1ffd7296 100644 --- a/homeassistant/components/vilfo/translations/no.json +++ b/homeassistant/components/vilfo/translations/no.json @@ -14,7 +14,7 @@ "access_token": "Tilgangstoken for Vilfo Router API", "host": "Ruter vertsnavn eller IP" }, - "description": "Konfigurer Vilfo Router-integreringen. Du trenger ditt Vilfo Router vertsnavn/IP og et API-tilgangstoken. Hvis du vil ha mer informasjon om denne integreringen og hvordan du f\u00e5r disse detaljene, kan du g\u00e5 til: https://www.home-assistant.io/integrations/vilfo", + "description": "Sett opp Vilfo Router-integreringen. Du trenger ditt Vilfo Router vertsnavn/IP og et API-tilgangstoken. Hvis du vil ha mer informasjon om denne integreringen og hvordan du f\u00e5r disse detaljene, kan du g\u00e5 til: https://www.home-assistant.io/integrations/vilfo", "title": "Koble til Vilfo Ruteren" } } diff --git a/homeassistant/components/vizio/translations/es-419.json b/homeassistant/components/vizio/translations/es-419.json index b8dc207c47b..d60f839d653 100644 --- a/homeassistant/components/vizio/translations/es-419.json +++ b/homeassistant/components/vizio/translations/es-419.json @@ -1,11 +1,39 @@ { "config": { + "abort": { + "already_setup": "Esta entrada ya se ha configurado.", + "updated_entry": "Esta entrada ya se configur\u00f3, pero el nombre, las aplicaciones o las opciones definidas en la configuraci\u00f3n no coinciden con la configuraci\u00f3n importada anteriormente, por lo que la entrada de configuraci\u00f3n se actualiz\u00f3 en consecuencia." + }, + "error": { + "cant_connect": "No se pudo conectar al dispositivo. [Revise los documentos] (https://www.home-assistant.io/integrations/vizio/) y vuelva a verificar que: \n - El dispositivo est\u00e1 encendido \n - El dispositivo est\u00e1 conectado a la red. \n - Los valores que complet\u00f3 son precisos \n antes de intentar volver a enviar.", + "complete_pairing failed": "No se puede completar el emparejamiento. Aseg\u00farese de que el PIN que proporcion\u00f3 sea correcto y que el televisor siga encendido y conectado a la red antes de volver a enviarlo.", + "host_exists": "Dispositivo VIZIO con el host especificado ya configurado.", + "name_exists": "Dispositivo VIZIO con el nombre especificado ya configurado." + }, "step": { + "pair_tv": { + "data": { + "pin": "PIN" + }, + "description": "Su televisi\u00f3n debe mostrar un c\u00f3digo. Ingrese ese c\u00f3digo en el formulario y luego contin\u00fae con el siguiente paso para completar el emparejamiento.", + "title": "Proceso de emparejamiento completo" + }, + "pairing_complete": { + "description": "Su dispositivo VIZIO SmartCast ahora est\u00e1 conectado a Home Assistant.", + "title": "Emparejamiento completo" + }, + "pairing_complete_import": { + "description": "Su VIZIO SmartCast TV ahora est\u00e1 conectado a Home Assistant. \n\nSu token de acceso es '** {access_token} **'.", + "title": "Emparejamiento completo" + }, "user": { "data": { + "access_token": "Token de acceso", "device_class": "Tipo de dispositivo", + "host": ":", "name": "Nombre" }, + "description": "Solo se necesita un token de acceso para televisores. Si est\u00e1 configurando un televisor y a\u00fan no tiene un token de acceso, d\u00e9jelo en blanco para realizar un proceso de emparejamiento.", "title": "Configurar el dispositivo VIZIO SmartCast" } } @@ -15,8 +43,10 @@ "init": { "data": { "apps_to_include_or_exclude": "Aplicaciones para incluir o excluir", - "include_or_exclude": "\u00bfIncluir o excluir aplicaciones?" + "include_or_exclude": "\u00bfIncluir o excluir aplicaciones?", + "volume_step": "Tama\u00f1o de incremento de volumen" }, + "description": "Si tiene un Smart TV, opcionalmente puede filtrar su lista de origen eligiendo qu\u00e9 aplicaciones incluir o excluir en su lista de origen.", "title": "Actualizar las opciones de VIZIO SmartCast" } } diff --git a/homeassistant/components/vizio/translations/no.json b/homeassistant/components/vizio/translations/no.json index 3479a013084..ddeb75c156e 100644 --- a/homeassistant/components/vizio/translations/no.json +++ b/homeassistant/components/vizio/translations/no.json @@ -19,7 +19,7 @@ "title": "Fullf\u00f8r Sammenkoblings Prosessen" }, "pairing_complete": { - "description": "Din VIZIO SmartCast enheten er n\u00e5 koblet til Hjemme-Assistent.", + "description": "Din VIZIO SmartCast enheten er n\u00e5 koblet til Home Assistant.", "title": "Sammenkoblingen Er Fullf\u00f8rt" }, "pairing_complete_import": { diff --git a/homeassistant/components/withings/translations/es-419.json b/homeassistant/components/withings/translations/es-419.json index 4fc2dec0ac2..d06470b213f 100644 --- a/homeassistant/components/withings/translations/es-419.json +++ b/homeassistant/components/withings/translations/es-419.json @@ -1,7 +1,23 @@ { "config": { + "abort": { + "authorize_url_timeout": "Tiempo de espera agotado para generar la URL de autorizaci\u00f3n.", + "missing_configuration": "La integraci\u00f3n de Withings no est\u00e1 configurada. Por favor, siga la documentaci\u00f3n." + }, "create_entry": { "default": "Autenticado correctamente con Withings para el perfil seleccionado." + }, + "step": { + "pick_implementation": { + "title": "Seleccione el m\u00e9todo de autenticaci\u00f3n" + }, + "profile": { + "data": { + "profile": "Perfil" + }, + "description": "\u00bfQu\u00e9 perfil seleccion\u00f3 en el sitio web de Withings? Es importante que los perfiles coincidan, de lo contrario los datos se etiquetar\u00e1n incorrectamente.", + "title": "Perfil del usuario." + } } } } \ No newline at end of file diff --git a/homeassistant/components/wled/translations/es-419.json b/homeassistant/components/wled/translations/es-419.json index a9107341e37..b5973638373 100644 --- a/homeassistant/components/wled/translations/es-419.json +++ b/homeassistant/components/wled/translations/es-419.json @@ -10,6 +10,9 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { + "data": { + "host": "Host o direcci\u00f3n IP" + }, "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, diff --git a/homeassistant/components/wled/translations/no.json b/homeassistant/components/wled/translations/no.json index e0c165b352c..fd3b5f94cbe 100644 --- a/homeassistant/components/wled/translations/no.json +++ b/homeassistant/components/wled/translations/no.json @@ -7,13 +7,13 @@ "error": { "connection_error": "Kunne ikke koble til WLED-enheten." }, - "flow_title": "", + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "Vert eller IP-adresse" }, - "description": "Konfigurer WLED til \u00e5 integreres med Home Assistant.", + "description": "Sett opp WLED til \u00e5 integreres med Home Assistant.", "title": "Linken din WLED" }, "zeroconf_confirm": { diff --git a/homeassistant/components/wwlln/translations/es-419.json b/homeassistant/components/wwlln/translations/es-419.json index 1732a5b43bc..11ae8c64359 100644 --- a/homeassistant/components/wwlln/translations/es-419.json +++ b/homeassistant/components/wwlln/translations/es-419.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Esta ubicaci\u00f3n ya est\u00e1 registrada." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/es-419.json b/homeassistant/components/xiaomi_miio/translations/es-419.json new file mode 100644 index 00000000000..4178a80a347 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/es-419.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "connect_error": "No se pudo conectar, intente nuevamente", + "no_device_selected": "Ning\u00fan dispositivo seleccionado, seleccione un dispositivo." + }, + "step": { + "gateway": { + "data": { + "host": "Direcci\u00f3n IP", + "name": "Nombre de la puerta de enlace", + "token": "Token API" + }, + "description": "Necesitar\u00e1 el token API, consulte https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token para obtener instrucciones.", + "title": "Conectarse a una puerta de enlace Xiaomi" + }, + "user": { + "data": { + "gateway": "Conectarse a una puerta de enlace Xiaomi" + }, + "description": "Seleccione a qu\u00e9 dispositivo desea conectarse.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json new file mode 100644 index 00000000000..c603eef4cac --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "error": { + "connect_error": "Impossible de se connecter, veuillez r\u00e9essayer", + "no_device_selected": "Aucun appareil s\u00e9lectionn\u00e9, veuillez s\u00e9lectionner un appareil." + }, + "step": { + "gateway": { + "data": { + "host": "adresse IP", + "name": "Nom de la passerelle", + "token": "Jeton d'API" + }, + "description": "Vous aurez besoin du jeton API, voir https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token pour les instructions.", + "title": "Se connecter \u00e0 la passerelle Xiaomi" + }, + "user": { + "data": { + "gateway": "Se connecter \u00e0 la passerelle Xiaomi" + }, + "description": "S\u00e9lectionnez \u00e0 quel appareil vous souhaitez vous connecter.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hans.json b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json new file mode 100644 index 00000000000..254256f9ac9 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86" + }, + "error": { + "connect_error": "\u8fde\u63a5\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5", + "no_device_selected": "\u672a\u9009\u62e9\u8bbe\u5907\uff0c\u8bf7\u9009\u62e9\u4e00\u4e2a\u8bbe\u5907\u3002" + }, + "step": { + "gateway": { + "data": { + "host": "IP \u5730\u5740", + "name": "\u7f51\u5173\u540d\u79f0", + "token": "API Token" + }, + "description": "\u60a8\u9700\u8981\u83b7\u53d6 API Token\u3002\u5982\u9700\u5e2e\u52a9\uff0c\u8bf7\u53c2\u9605\u4ee5\u4e0b\u94fe\u63a5\uff1ahttps://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token", + "title": "\u8fde\u63a5\u5230\u5c0f\u7c73\u7f51\u5173" + }, + "user": { + "data": { + "gateway": "\u8fde\u63a5\u5230\u5c0f\u7c73\u7f51\u5173" + }, + "description": "\u8bf7\u9009\u62e9\u8981\u8fde\u63a5\u7684\u8bbe\u5907\u3002", + "title": "\u5c0f\u7c73 Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/es-419.json b/homeassistant/components/zha/translations/es-419.json index 81803aa8cf4..f072420dfc5 100644 --- a/homeassistant/components/zha/translations/es-419.json +++ b/homeassistant/components/zha/translations/es-419.json @@ -17,7 +17,28 @@ } }, "device_automation": { + "action_type": { + "squawk": "Graznido", + "warn": "Advertir" + }, "trigger_subtype": { + "both_buttons": "Ambos botones", + "button_1": "Primer bot\u00f3n", + "button_2": "Segundo bot\u00f3n", + "button_3": "Tercer bot\u00f3n", + "button_4": "Cuarto bot\u00f3n", + "button_5": "Quinto bot\u00f3n", + "button_6": "Sexto bot\u00f3n", + "close": "Cerrar", + "dim_down": "Bajar la intensidad", + "dim_up": "Aumentar intensidad", + "face_1": "con la cara 1 activada", + "face_2": "con la cara 2 activada", + "face_3": "con la cara 3 activada", + "face_4": "con la cara 4 activada", + "face_5": "con la cara 5 activada", + "face_6": "con la cara 6 activada", + "face_any": "Con cualquier cara/especificada(s) activada(s)", "left": "Izquierda", "open": "Abrir", "right": "Derecha", @@ -31,7 +52,23 @@ "device_rotated": "Dispositivo girado \"{subtype}\"", "device_shaken": "Dispositivo agitado", "device_slid": "Dispositivo deslizado \"{subtype}\"", - "device_tilted": "Dispositivo inclinado" + "device_tilted": "Dispositivo inclinado", + "remote_button_alt_double_press": "El bot\u00f3n \"{subtype}\" fue presionado 2 veces (modo alternativo)", + "remote_button_alt_long_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado continuamente (modo alternativo)", + "remote_button_alt_long_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\" despu\u00e9s de una pulsaci\u00f3n prolongada (modo alternativo)", + "remote_button_alt_quadruple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 4 veces (modo alternativo)", + "remote_button_alt_quintuple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 5 veces (modo alternativo)", + "remote_button_alt_short_press": "El bot\u00f3n \"{subtype}\" ha sido presionado (modo alternativo)", + "remote_button_alt_short_release": "El bot\u00f3n \"{subtype}\" ha sido soltado (modo alternativo)", + "remote_button_alt_triple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 3 veces (modo alternativo)", + "remote_button_double_press": "El bot\u00f3n \"{subtype}\" fue presionado 2 veces", + "remote_button_long_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado continuamente", + "remote_button_long_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\" despu\u00e9s de una pulsaci\u00f3n prolongada", + "remote_button_quadruple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 4 veces", + "remote_button_quintuple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 5 veces", + "remote_button_short_press": "Se presion\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_button_short_release": "Se solt\u00f3 el bot\u00f3n \"{subtype}\"", + "remote_button_triple_press": "El bot\u00f3n \"{subtype}\" ha sido pulsado 3 veces" } } } \ No newline at end of file From 9efca0079c42dd45d46c71de732b7fd80a46e66d Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 3 May 2020 02:09:44 +0200 Subject: [PATCH 225/511] Upgrades requests-mock to 1.8.0 (#35093) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b3525b05c08..3f565d2c3a8 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -17,5 +17,5 @@ pytest-cov==2.8.1 pytest-sugar==0.9.3 pytest-timeout==1.3.4 pytest==5.4.1 -requests_mock==1.7.0 +requests_mock==1.8.0 responses==0.10.6 From 984a2769dbe4caa92dae068ec60625491ea3de19 Mon Sep 17 00:00:00 2001 From: ochlocracy <5885236+ochlocracy@users.noreply.github.com> Date: Sat, 2 May 2020 20:39:51 -0400 Subject: [PATCH 226/511] Correct error message in Microsoft Face (#35096) --- homeassistant/components/microsoft_face/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index 16b25e4f85d..208cecf6d3b 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -189,7 +189,9 @@ async def async_setup(hass, config): binary=True, ) except HomeAssistantError as err: - _LOGGER.error("Can't delete person '%s' with error: %s", p_id, err) + _LOGGER.error( + "Can't add an image of a person '%s' with error: %s", p_id, err + ) hass.services.async_register( DOMAIN, SERVICE_FACE_PERSON, async_face_person, schema=SCHEMA_FACE_SERVICE From aeb891649e235bccf8771cf53e67c6ce99cea78f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 3 May 2020 02:54:16 +0200 Subject: [PATCH 227/511] Add zwave mqtt (#34987) --- .coveragerc | 4 + .pre-commit-config.yaml | 4 +- CODEOWNERS | 1 + .../components/zwave_mqtt/__init__.py | 328 ++++++++++++++++++ .../components/zwave_mqtt/config_flow.py | 24 ++ homeassistant/components/zwave_mqtt/const.py | 43 +++ .../components/zwave_mqtt/discovery.py | 83 +++++ homeassistant/components/zwave_mqtt/entity.py | 289 +++++++++++++++ .../components/zwave_mqtt/manifest.json | 17 + .../components/zwave_mqtt/services.py | 53 +++ .../components/zwave_mqtt/services.yaml | 14 + .../components/zwave_mqtt/strings.json | 14 + homeassistant/components/zwave_mqtt/switch.py | 41 +++ .../zwave_mqtt/translations/en.json | 14 + homeassistant/generated/config_flows.py | 3 +- requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/zwave_mqtt/__init__.py | 1 + tests/components/zwave_mqtt/common.py | 57 +++ tests/components/zwave_mqtt/conftest.py | 40 +++ .../components/zwave_mqtt/test_config_flow.py | 53 +++ tests/components/zwave_mqtt/test_init.py | 62 ++++ tests/components/zwave_mqtt/test_scenes.py | 88 +++++ tests/components/zwave_mqtt/test_switch.py | 41 +++ .../zwave_mqtt/generic_network_dump.csv | 276 +++++++++++++++ tests/fixtures/zwave_mqtt/switch.json | 25 ++ 26 files changed, 1578 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/zwave_mqtt/__init__.py create mode 100644 homeassistant/components/zwave_mqtt/config_flow.py create mode 100644 homeassistant/components/zwave_mqtt/const.py create mode 100644 homeassistant/components/zwave_mqtt/discovery.py create mode 100644 homeassistant/components/zwave_mqtt/entity.py create mode 100644 homeassistant/components/zwave_mqtt/manifest.json create mode 100644 homeassistant/components/zwave_mqtt/services.py create mode 100644 homeassistant/components/zwave_mqtt/services.yaml create mode 100644 homeassistant/components/zwave_mqtt/strings.json create mode 100644 homeassistant/components/zwave_mqtt/switch.py create mode 100644 homeassistant/components/zwave_mqtt/translations/en.json create mode 100644 tests/components/zwave_mqtt/__init__.py create mode 100644 tests/components/zwave_mqtt/common.py create mode 100644 tests/components/zwave_mqtt/conftest.py create mode 100644 tests/components/zwave_mqtt/test_config_flow.py create mode 100644 tests/components/zwave_mqtt/test_init.py create mode 100644 tests/components/zwave_mqtt/test_scenes.py create mode 100644 tests/components/zwave_mqtt/test_switch.py create mode 100644 tests/fixtures/zwave_mqtt/generic_network_dump.csv create mode 100644 tests/fixtures/zwave_mqtt/switch.json diff --git a/.coveragerc b/.coveragerc index e36d6b252bd..2a66d2f2560 100644 --- a/.coveragerc +++ b/.coveragerc @@ -878,6 +878,10 @@ omit = homeassistant/components/zoneminder/* homeassistant/components/supla/* homeassistant/components/zwave/util.py + homeassistant/components/zwave_mqtt/__init__.py + homeassistant/components/zwave_mqtt/discovery.py + homeassistant/components/zwave_mqtt/entity.py + homeassistant/components/zwave_mqtt/services.py [report] # Regexes for lines to exclude from consideration diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05e062e43b9..8d7efbf00f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,9 +18,9 @@ repos: - id: codespell args: - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing - - --skip="./.*,*.json" + - --skip="./.*,*.csv,*.json" - --quiet-level=2 - exclude_types: [json] + exclude_types: [csv, json] - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 hooks: diff --git a/CODEOWNERS b/CODEOWNERS index a021a4a28ed..a537e562525 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -463,6 +463,7 @@ homeassistant/components/zha/* @dmulcahey @adminiuga homeassistant/components/zone/* @home-assistant/core homeassistant/components/zoneminder/* @rohankapoorcom homeassistant/components/zwave/* @home-assistant/z-wave +homeassistant/components/zwave_mqtt/* @cgarwood @marcelveldt @MartinHjelmare # Individual files homeassistant/components/demo/weather @fabaff diff --git a/homeassistant/components/zwave_mqtt/__init__.py b/homeassistant/components/zwave_mqtt/__init__.py new file mode 100644 index 00000000000..8b124574750 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/__init__.py @@ -0,0 +1,328 @@ +"""The zwave_mqtt integration.""" +import asyncio +import json +import logging + +from openzwavemqtt import OZWManager, OZWOptions +from openzwavemqtt.const import ( + EVENT_INSTANCE_EVENT, + EVENT_NODE_ADDED, + EVENT_NODE_CHANGED, + EVENT_NODE_REMOVED, + EVENT_VALUE_ADDED, + EVENT_VALUE_CHANGED, + EVENT_VALUE_REMOVED, + CommandClass, + ValueType, +) +from openzwavemqtt.models.node import OZWNode +from openzwavemqtt.models.value import OZWValue +import voluptuous as vol + +from homeassistant.components import mqtt +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from . import const +from .const import DATA_UNSUBSCRIBE, DOMAIN, PLATFORMS, TOPIC_OPENZWAVE +from .discovery import DISCOVERY_SCHEMAS, check_node_schema, check_value_schema +from .entity import ( + ZWaveDeviceEntityValues, + create_device_id, + create_device_name, + create_value_id, +) +from .services import ZWaveServices + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) +DATA_DEVICES = "zwave-mqtt-devices" + + +async def async_setup(hass: HomeAssistant, config: dict): + """Initialize basic config of zwave_mqtt component.""" + if "mqtt" not in hass.config.components: + _LOGGER.error("MQTT integration is not set up") + return False + hass.data[DOMAIN] = {} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up zwave_mqtt from a config entry.""" + zwave_mqtt_data = hass.data[DOMAIN][entry.entry_id] = {} + zwave_mqtt_data[DATA_UNSUBSCRIBE] = [] + + data_nodes = {} + data_values = {} + removed_nodes = [] + + @callback + def send_message(topic, payload): + mqtt.async_publish(hass, topic, json.dumps(payload)) + + options = OZWOptions(send_message=send_message, topic_prefix=f"{TOPIC_OPENZWAVE}/") + manager = OZWManager(options) + + @callback + def async_node_added(node): + # Caution: This is also called on (re)start. + _LOGGER.debug("[NODE ADDED] node_id: %s", node.id) + data_nodes[node.id] = node + if node.id not in data_values: + data_values[node.id] = [] + + @callback + def async_node_changed(node): + _LOGGER.debug("[NODE CHANGED] node_id: %s", node.id) + data_nodes[node.id] = node + # notify devices about the node change + if node.id not in removed_nodes: + hass.async_create_task(async_handle_node_update(hass, node)) + + @callback + def async_node_removed(node): + _LOGGER.debug("[NODE REMOVED] node_id: %s", node.id) + data_nodes.pop(node.id) + # node added/removed events also happen on (re)starts of hass/mqtt/ozw + # cleanup device/entity registry if we know this node is permanently deleted + # entities itself are removed by the values logic + if node.id in removed_nodes: + hass.async_create_task(async_handle_remove_node(hass, node)) + removed_nodes.remove(node.id) + + @callback + def async_instance_event(message): + event = message["event"] + event_data = message["data"] + _LOGGER.debug("[INSTANCE EVENT]: %s - data: %s", event, event_data) + # The actual removal action of a Z-Wave node is reported as instance event + # Only when this event is detected we cleanup the device and entities from hass + if event == "removenode" and "Node" in event_data: + removed_nodes.append(event_data["Node"]) + + @callback + def async_value_added(value): + node = value.node + node_id = value.node.node_id + + # Filter out CommandClasses we're definitely not interested in. + if value.command_class in [ + CommandClass.CONFIGURATION, + CommandClass.VERSION, + CommandClass.MANUFACTURER_SPECIFIC, + ]: + return + + _LOGGER.debug( + "[VALUE ADDED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + + node_data_values = data_values[node_id] + + # Check if this value should be tracked by an existing entity + value_unique_id = create_value_id(value) + for values in node_data_values: + values.async_check_value(value) + if values.values_id == value_unique_id: + return # this value already has an entity + + # Run discovery on it and see if any entities need created + for schema in DISCOVERY_SCHEMAS: + if not check_node_schema(node, schema): + continue + if not check_value_schema( + value, schema[const.DISC_VALUES][const.DISC_PRIMARY] + ): + continue + + values = ZWaveDeviceEntityValues(hass, options, schema, value) + values.async_setup() + + # We create a new list and update the reference here so that + # the list can be safely iterated over in the main thread + data_values[node_id] = node_data_values + [values] + + @callback + def async_value_changed(value): + # if an entity belonging to this value needs updating, + # it's handled within the entity logic + _LOGGER.debug( + "[VALUE CHANGED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + # Handle a scene activation message + if value.command_class in [ + CommandClass.SCENE_ACTIVATION, + CommandClass.CENTRAL_SCENE, + ]: + async_handle_scene_activated(hass, value) + return + + @callback + def async_value_removed(value): + _LOGGER.debug( + "[VALUE REMOVED] node_id: %s - label: %s - value: %s - value_id: %s - CC: %s", + value.node.id, + value.label, + value.value, + value.value_id_key, + value.command_class, + ) + # signal all entities using this value for removal + value_unique_id = create_value_id(value) + async_dispatcher_send(hass, const.SIGNAL_DELETE_ENTITY, value_unique_id) + # remove value from our local list + node_data_values = data_values[value.node.id] + node_data_values[:] = [ + item for item in node_data_values if item.values_id != value_unique_id + ] + + # Listen to events for node and value changes + options.listen(EVENT_NODE_ADDED, async_node_added) + options.listen(EVENT_NODE_CHANGED, async_node_changed) + options.listen(EVENT_NODE_REMOVED, async_node_removed) + options.listen(EVENT_VALUE_ADDED, async_value_added) + options.listen(EVENT_VALUE_CHANGED, async_value_changed) + options.listen(EVENT_VALUE_REMOVED, async_value_removed) + options.listen(EVENT_INSTANCE_EVENT, async_instance_event) + + # Register Services + services = ZWaveServices(hass, manager) + services.async_register() + + @callback + def async_receive_message(msg): + manager.receive_message(msg.topic, msg.payload) + + async def start_platforms(): + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_setup(entry, component) + for component in PLATFORMS + ] + ) + zwave_mqtt_data[DATA_UNSUBSCRIBE].append( + await mqtt.async_subscribe( + hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message + ) + ) + + hass.async_create_task(start_platforms()) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + # cleanup platforms + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if not unload_ok: + return False + + # unsubscribe all listeners + for unsubscribe_listener in hass.data[DOMAIN][entry.entry_id][DATA_UNSUBSCRIBE]: + unsubscribe_listener() + hass.data[DOMAIN].pop(entry.entry_id) + + return True + + +async def async_handle_remove_node(hass: HomeAssistant, node: OZWNode): + """Handle the removal of a Z-Wave node, removing all traces in device/entity registry.""" + dev_registry = await get_dev_reg(hass) + # grab device in device registry attached to this node + dev_id = create_device_id(node) + device = dev_registry.async_get_device({(DOMAIN, dev_id)}, set()) + if not device: + return + devices_to_remove = [device.id] + # also grab slave devices (node instances) + for item in dev_registry.devices.values(): + if item.via_device_id == device.id: + devices_to_remove.append(item.id) + # remove all devices in registry related to this node + # note: removal of entity registry is handled by core + for dev_id in devices_to_remove: + dev_registry.async_remove_device(dev_id) + + +async def async_handle_node_update(hass: HomeAssistant, node: OZWNode): + """ + Handle a node updated event from OZW. + + Meaning some of the basic info like name/model is updated. + We want these changes to be pushed to the device registry. + """ + dev_registry = await get_dev_reg(hass) + # grab device in device registry attached to this node + dev_id = create_device_id(node) + device = dev_registry.async_get_device({(DOMAIN, dev_id)}, set()) + if not device: + return + # update device in device registry with (updated) info + for item in dev_registry.devices.values(): + if item.id != device.id and item.via_device_id != device.id: + continue + dev_name = create_device_name(node) + dev_registry.async_update_device( + item.id, + manufacturer=node.node_manufacturer_name, + model=node.node_product_name, + name=dev_name, + ) + + +@callback +def async_handle_scene_activated(hass: HomeAssistant, scene_value: OZWValue): + """Handle a (central) scene activation message.""" + node_id = scene_value.node.id + scene_id = scene_value.index + scene_label = scene_value.label + if scene_value.command_class == CommandClass.SCENE_ACTIVATION: + # legacy/network scene + scene_value_id = scene_value.value + scene_value_label = scene_value.label + else: + # central scene command + if scene_value.type != ValueType.LIST: + return + scene_value_label = scene_value.value["Selected"] + scene_value_id = scene_value.value["Selected_id"] + + _LOGGER.debug( + "[SCENE_ACTIVATED] node_id: %s - scene_id: %s - scene_value_id: %s", + node_id, + scene_id, + scene_value_id, + ) + # Simply forward it to the hass event bus + hass.bus.async_fire( + const.EVENT_SCENE_ACTIVATED, + { + const.ATTR_NODE_ID: node_id, + const.ATTR_SCENE_ID: scene_id, + const.ATTR_SCENE_LABEL: scene_label, + const.ATTR_SCENE_VALUE_ID: scene_value_id, + const.ATTR_SCENE_VALUE_LABEL: scene_value_label, + }, + ) diff --git a/homeassistant/components/zwave_mqtt/config_flow.py b/homeassistant/components/zwave_mqtt/config_flow.py new file mode 100644 index 00000000000..ff6ab21994f --- /dev/null +++ b/homeassistant/components/zwave_mqtt/config_flow.py @@ -0,0 +1,24 @@ +"""Config flow for zwave_mqtt integration.""" +from homeassistant import config_entries + +from .const import DOMAIN # pylint:disable=unused-import + +TITLE = "Z-Wave MQTT" + + +class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for zwave_mqtt.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="one_instance_allowed") + if "mqtt" not in self.hass.config.components: + return self.async_abort(reason="mqtt_required") + if user_input is not None: + return self.async_create_entry(title=TITLE, data={}) + + return self.async_show_form(step_id="user") diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/zwave_mqtt/const.py new file mode 100644 index 00000000000..4391d53ac4c --- /dev/null +++ b/homeassistant/components/zwave_mqtt/const.py @@ -0,0 +1,43 @@ +"""Constants for the zwave_mqtt integration.""" +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN + +DOMAIN = "zwave_mqtt" +DATA_UNSUBSCRIBE = "unsubscribe" +PLATFORMS = [SWITCH_DOMAIN] + +# MQTT Topics +TOPIC_OPENZWAVE = "OpenZWave" + +# Common Attributes +ATTR_INSTANCE_ID = "instance_id" +ATTR_SECURE = "secure" +ATTR_NODE_ID = "node_id" +ATTR_SCENE_ID = "scene_id" +ATTR_SCENE_LABEL = "scene_label" +ATTR_SCENE_VALUE_ID = "scene_value_id" +ATTR_SCENE_VALUE_LABEL = "scene_value_label" + +# Service specific +SERVICE_ADD_NODE = "add_node" +SERVICE_REMOVE_NODE = "remove_node" + +# Home Assistant Events +EVENT_SCENE_ACTIVATED = f"{DOMAIN}.scene_activated" + +# Signals +SIGNAL_DELETE_ENTITY = f"{DOMAIN}_delete_entity" + +# Discovery Information +DISC_COMMAND_CLASS = "command_class" +DISC_COMPONENT = "component" +DISC_GENERIC_DEVICE_CLASS = "generic_device_class" +DISC_GENRE = "genre" +DISC_INDEX = "index" +DISC_INSTANCE = "instance" +DISC_NODE_ID = "node_id" +DISC_OPTIONAL = "optional" +DISC_PRIMARY = "primary" +DISC_SCHEMAS = "schemas" +DISC_SPECIFIC_DEVICE_CLASS = "specific_device_class" +DISC_TYPE = "type" +DISC_VALUES = "values" diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/zwave_mqtt/discovery.py new file mode 100644 index 00000000000..e257e43b678 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/discovery.py @@ -0,0 +1,83 @@ +"""Map Z-Wave nodes and values to Home Assistant entities.""" +import openzwavemqtt.const as const_ozw +from openzwavemqtt.const import CommandClass, ValueGenre, ValueType + +from . import const + +DISCOVERY_SCHEMAS = ( + { # Switch platform + const.DISC_COMPONENT: "switch", + const.DISC_GENERIC_DEVICE_CLASS: ( + const_ozw.GENERIC_TYPE_METER, + const_ozw.GENERIC_TYPE_SENSOR_ALARM, + const_ozw.GENERIC_TYPE_SENSOR_BINARY, + const_ozw.GENERIC_TYPE_SWITCH_BINARY, + const_ozw.GENERIC_TYPE_ENTRY_CONTROL, + const_ozw.GENERIC_TYPE_SENSOR_MULTILEVEL, + const_ozw.GENERIC_TYPE_SWITCH_MULTILEVEL, + const_ozw.GENERIC_TYPE_GENERIC_CONTROLLER, + const_ozw.GENERIC_TYPE_SWITCH_REMOTE, + const_ozw.GENERIC_TYPE_REPEATER_SLAVE, + const_ozw.GENERIC_TYPE_THERMOSTAT, + const_ozw.GENERIC_TYPE_WALL_CONTROLLER, + ), + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: (CommandClass.SWITCH_BINARY,), + const.DISC_TYPE: ValueType.BOOL, + const.DISC_GENRE: ValueGenre.USER, + } + }, + }, +) + + +def check_node_schema(node, schema): + """Check if node matches the passed node schema.""" + if const.DISC_NODE_ID in schema and node.node_id not in schema[const.DISC_NODE_ID]: + return False + if const.DISC_GENERIC_DEVICE_CLASS in schema and not eq_or_in( + node.node_generic, schema[const.DISC_GENERIC_DEVICE_CLASS] + ): + return False + if const.DISC_SPECIFIC_DEVICE_CLASS in schema and not eq_or_in( + node.node_specific, schema[const.DISC_SPECIFIC_DEVICE_CLASS] + ): + return False + return True + + +def check_value_schema(value, schema): + """Check if the value matches the passed value schema.""" + if ( + const.DISC_COMMAND_CLASS in schema + and value.parent.command_class_id not in schema[const.DISC_COMMAND_CLASS] + ): + return False + if const.DISC_TYPE in schema and not eq_or_in(value.type, schema[const.DISC_TYPE]): + return False + if const.DISC_GENRE in schema and not eq_or_in( + value.genre, schema[const.DISC_GENRE] + ): + return False + if const.DISC_INDEX in schema and not eq_or_in( + value.index, schema[const.DISC_INDEX] + ): + return False + if const.DISC_INSTANCE in schema and not eq_or_in( + value.instance, schema[const.DISC_INSTANCE] + ): + return False + if const.DISC_SCHEMAS in schema: + found = False + for schema_item in schema[const.DISC_SCHEMAS]: + found = found or check_value_schema(value, schema_item) + if not found: + return False + + return True + + +def eq_or_in(val, options): + """Return True if options contains value or if value is equal to options.""" + return val in options if isinstance(options, tuple) else val == options diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py new file mode 100644 index 00000000000..0f23a573eed --- /dev/null +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -0,0 +1,289 @@ +"""Generic Z-Wave Entity Classes.""" + +import copy +import logging + +from openzwavemqtt.const import ( + EVENT_INSTANCE_STATUS_CHANGED, + EVENT_VALUE_CHANGED, + OZW_READY_STATES, +) +from openzwavemqtt.models.node import OZWNode +from openzwavemqtt.models.value import OZWValue + +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity import Entity + +from . import const +from .const import DOMAIN, PLATFORMS +from .discovery import check_node_schema, check_value_schema + +_LOGGER = logging.getLogger(__name__) + + +class ZWaveDeviceEntityValues: + """Manages entity access to the underlying Z-Wave value objects.""" + + def __init__(self, hass, options, schema, primary_value): + """Initialize the values object with the passed entity schema.""" + self._hass = hass + self._entity_created = False + self._schema = copy.deepcopy(schema) + self._values = {} + self.options = options + + # Go through values listed in the discovery schema, initialize them, + # and add a check to the schema to make sure the Instance matches. + for name, disc_settings in self._schema[const.DISC_VALUES].items(): + self._values[name] = None + disc_settings[const.DISC_INSTANCE] = [primary_value.instance] + + self._values[const.DISC_PRIMARY] = primary_value + self._node = primary_value.node + self._schema[const.DISC_NODE_ID] = [self._node.node_id] + + def async_setup(self): + """Set up values instance.""" + # Check values that have already been discovered for node + # and see if they match the schema and need added to the entity. + for value in self._node.values(): + self.async_check_value(value) + + # Check if all the _required_ values in the schema are present and + # create the entity. + self._async_check_entity_ready() + + def __getattr__(self, name): + """Get the specified value for this entity.""" + return self._values.get(name, None) + + def __iter__(self): + """Allow iteration over all values.""" + return iter(self._values.values()) + + def __contains__(self, name): + """Check if the specified name/key exists in the values.""" + return name in self._values + + @callback + def async_check_value(self, value): + """Check if the new value matches a missing value for this entity. + + If a match is found, it is added to the values mapping. + """ + # Make sure the node matches the schema for this entity. + if not check_node_schema(value.node, self._schema): + return + + # Go through the possible values for this entity defined by the schema. + for name in self._values: + # Skip if it's already been added. + if self._values[name] is not None: + continue + # Skip if the value doesn't match the schema. + if not check_value_schema(value, self._schema[const.DISC_VALUES][name]): + continue + + # Add value to mapping. + self._values[name] = value + + # If the entity has already been created, notify it of the new value. + if self._entity_created: + async_dispatcher_send( + self._hass, f"{DOMAIN}_{self.values_id}_value_added" + ) + + # Check if entity has all required values and create the entity if needed. + self._async_check_entity_ready() + + @callback + def _async_check_entity_ready(self): + """Check if all required values are discovered and create entity.""" + # Abort if the entity has already been created + if self._entity_created: + return + + # Go through values defined in the schema and abort if a required value is missing. + for name, disc_settings in self._schema[const.DISC_VALUES].items(): + if self._values[name] is None and not disc_settings.get( + const.DISC_OPTIONAL + ): + return + + # We have all the required values, so create the entity. + component = self._schema[const.DISC_COMPONENT] + + _LOGGER.debug( + "Adding Node_id=%s Generic_command_class=%s, " + "Specific_command_class=%s, " + "Command_class=%s, Index=%s, Value type=%s, " + "Genre=%s as %s", + self._node.node_id, + self._node.node_generic, + self._node.node_specific, + self.primary.command_class, + self.primary.index, + self.primary.type, + self.primary.genre, + component, + ) + self._entity_created = True + + if component in PLATFORMS: + async_dispatcher_send(self._hass, f"{DOMAIN}_new_{component}", self) + + @property + def values_id(self): + """Identification for this values collection.""" + return create_value_id(self.primary) + + +class ZWaveDeviceEntity(Entity): + """Generic Entity Class for a Z-Wave Device.""" + + def __init__(self, values): + """Initialize a generic Z-Wave device entity.""" + self.values = values + self.options = values.options + + @callback + def on_value_update(self): + """Call when a value is added/updated in the entity EntityValues Collection. + + To be overridden by platforms needing this event. + """ + + async def async_added_to_hass(self): + """Call when entity is added.""" + # add dispatcher and OZW listeners callbacks, + self.options.listen(EVENT_VALUE_CHANGED, self._value_changed) + self.options.listen(EVENT_INSTANCE_STATUS_CHANGED, self._instance_updated) + # add to on_remove so they will be cleaned up on entity removal + self.async_on_remove( + async_dispatcher_connect( + self.hass, const.SIGNAL_DELETE_ENTITY, self._delete_callback + ) + ) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"{DOMAIN}_{self.values.values_id}_value_added", + self._value_added, + ) + ) + + @property + def device_info(self): + """Return device information for the device registry.""" + node = self.values.primary.node + node_instance = self.values.primary.instance + dev_id = create_device_id(node, self.values.primary.instance) + device_info = { + "identifiers": {(DOMAIN, dev_id)}, + "name": create_device_name(node), + "manufacturer": node.node_manufacturer_name, + "model": node.node_product_name, + } + # device with multiple instances is split up into virtual devices for each instance + if node_instance > 1: + parent_dev_id = create_device_id(node) + device_info["name"] += f" - Instance {node_instance}" + device_info["via_device"] = (DOMAIN, parent_dev_id) + return device_info + + @property + def device_state_attributes(self): + """Return the device specific state attributes.""" + return {const.ATTR_NODE_ID: self.values.primary.node.node_id} + + @property + def name(self): + """Return the name of the entity.""" + node = self.values.primary.node + return f"{create_device_name(node)}: {self.values.primary.label}" + + @property + def unique_id(self): + """Return the unique_id of the entity.""" + return self.values.values_id + + @property + def available(self) -> bool: + """Return entity availability.""" + # Use OZW Daemon status for availability. + instance_status = self.values.primary.ozw_instance.get_status() + return instance_status and instance_status.status in ( + state.value for state in OZW_READY_STATES + ) + + @callback + def _value_changed(self, value): + """Call when a value from ZWaveDeviceEntityValues is changed. + + Should not be overridden by subclasses. + """ + if value.value_id_key in (v.value_id_key for v in self.values if v): + self.on_value_update() + self.async_write_ha_state() + + @callback + def _value_added(self): + """Call when a value from ZWaveDeviceEntityValues is added. + + Should not be overridden by subclasses. + """ + self.on_value_update() + + @callback + def _instance_updated(self, new_status): + """Call when the instance status changes. + + Should not be overridden by subclasses. + """ + self.on_value_update() + self.async_write_ha_state() + + async def _delete_callback(self, values_id): + """Remove this entity.""" + if not self.values: + return # race condition: delete already requested + if values_id == self.values.values_id: + await self.async_remove() + + async def async_will_remove_from_hass(self) -> None: + """Call when entity will be removed from hass.""" + # cleanup OZW listeners + self.options.listeners[EVENT_VALUE_CHANGED].remove(self._value_changed) + self.options.listeners[EVENT_INSTANCE_STATUS_CHANGED].remove( + self._instance_updated + ) + + +def create_device_name(node: OZWNode): + """Generate sensible (short) default device name from a OZWNode.""" + if node.meta_data["Name"]: + dev_name = node.meta_data["Name"] + elif node.node_product_name: + dev_name = node.node_product_name + elif node.node_device_type_string: + dev_name = node.node_device_type_string + else: + dev_name = node.specific_string + return dev_name + + +def create_device_id(node: OZWNode, node_instance: int = 1): + """Generate unique device_id from a OZWNode.""" + ozw_instance = node.parent.id + dev_id = f"{ozw_instance}.{node.node_id}.{node_instance}" + return dev_id + + +def create_value_id(value: OZWValue): + """Generate unique value_id from an OZWValue.""" + # [OZW_INSTANCE_ID]-[NODE_ID]-[VALUE_ID_KEY] + return f"{value.node.parent.id}-{value.node.id}-{value.value_id_key}" diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json new file mode 100644 index 00000000000..8d067bf5c35 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/manifest.json @@ -0,0 +1,17 @@ +{ + "domain": "zwave_mqtt", + "name": "Z-Wave over MQTT", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/zwave_mqtt", + "requirements": [ + "python-openzwave-mqtt==1.0.1" + ], + "after_dependencies": [ + "mqtt" + ], + "codeowners": [ + "@cgarwood", + "@marcelveldt", + "@MartinHjelmare" + ] +} diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/zwave_mqtt/services.py new file mode 100644 index 00000000000..a2f4ca2e553 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/services.py @@ -0,0 +1,53 @@ +"""Methods and classes related to executing Z-Wave commands and publishing these to hass.""" +import voluptuous as vol + +from homeassistant.core import callback + +from . import const + + +class ZWaveServices: + """Class that holds our services ( Zwave Commands) that should be published to hass.""" + + def __init__(self, hass, manager): + """Initialize with both hass and ozwmanager objects.""" + self._hass = hass + self._manager = manager + + @callback + def async_register(self): + """Register all our services.""" + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_ADD_NODE, + self.async_add_node, + schema=vol.Schema( + { + vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int), + vol.Optional(const.ATTR_SECURE, default=False): vol.Coerce(bool), + } + ), + ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_REMOVE_NODE, + self.async_remove_node, + schema=vol.Schema( + {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} + ), + ) + + @callback + def async_add_node(self, service): + """Enter inclusion mode on the controller.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + secure = service.data[const.ATTR_SECURE] + instance = self._manager.get_instance(instance_id) + instance.add_node(secure) + + @callback + def async_remove_node(self, service): + """Enter exclusion mode on the controller.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + instance = self._manager.get_instance(instance_id) + instance.remove_node() diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/zwave_mqtt/services.yaml new file mode 100644 index 00000000000..92685f1a463 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/services.yaml @@ -0,0 +1,14 @@ +# Describes the format for available Z-Wave services +add_node: + description: Add a new node to the Z-Wave network. + fields: + secure: + description: Add the new node with secure communications. Secure network key must be set, this process will fallback to add_node (unsecure) for unsupported devices. Note that unsecure devices can't directly talk to secure devices. + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + +remove_node: + description: Remove a node from the Z-Wave network. Will set the controller into exclusion mode. + fields: + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. diff --git a/homeassistant/components/zwave_mqtt/strings.json b/homeassistant/components/zwave_mqtt/strings.json new file mode 100644 index 00000000000..949b545086b --- /dev/null +++ b/homeassistant/components/zwave_mqtt/strings.json @@ -0,0 +1,14 @@ +{ + "title": "Z-Wave over MQTT", + "config": { + "step": { + "user": { + "title": "Confirm set up" + } + }, + "abort": { + "one_instance_allowed": "The integration only supports one Z-Wave instance", + "mqtt_required": "The MQTT integration is not set up" + } + } +} diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/zwave_mqtt/switch.py new file mode 100644 index 00000000000..c1a0ef353b8 --- /dev/null +++ b/homeassistant/components/zwave_mqtt/switch.py @@ -0,0 +1,41 @@ +"""Representation of Z-Wave switches.""" +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SwitchEntity +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave switch from config entry.""" + + @callback + def async_add_switch(value): + """Add Z-Wave Switch.""" + switch = ZWaveSwitch(value) + + async_add_entities([switch]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect( + hass, f"{DOMAIN}_new_{SWITCH_DOMAIN}", async_add_switch + ) + ) + + +class ZWaveSwitch(ZWaveDeviceEntity, SwitchEntity): + """Representation of a Z-Wave switch.""" + + @property + def is_on(self): + """Return a boolean for the state of the switch.""" + return bool(self.values.primary.value) + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + self.values.primary.send_value(True) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + self.values.primary.send_value(False) diff --git a/homeassistant/components/zwave_mqtt/translations/en.json b/homeassistant/components/zwave_mqtt/translations/en.json new file mode 100644 index 00000000000..4b5b44a76bb --- /dev/null +++ b/homeassistant/components/zwave_mqtt/translations/en.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "mqtt_required": "The MQTT integration is not set up", + "one_instance_allowed": "The integration only supports one Z-Wave instance" + }, + "step": { + "user": { + "title": "Confirm set up" + } + } + }, + "title": "Z-Wave over MQTT" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 1f08da03648..0fde3dc9676 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -146,5 +146,6 @@ FLOWS = [ "wwlln", "xiaomi_miio", "zha", - "zwave" + "zwave", + "zwave_mqtt" ] diff --git a/requirements_all.txt b/requirements_all.txt index 5623c120c15..6b896f0bd85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1679,6 +1679,9 @@ python-nest==4.1.0 # homeassistant.components.nmap_tracker python-nmap==0.6.1 +# homeassistant.components.zwave_mqtt +python-openzwave-mqtt==1.0.1 + # homeassistant.components.qbittorrent python-qbittorrent==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 89eacf76e70..bfeaad74448 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -661,6 +661,9 @@ python-miio==0.5.0.1 # homeassistant.components.nest python-nest==4.1.0 +# homeassistant.components.zwave_mqtt +python-openzwave-mqtt==1.0.1 + # homeassistant.components.synology_dsm python-synology==0.8.0 diff --git a/tests/components/zwave_mqtt/__init__.py b/tests/components/zwave_mqtt/__init__.py new file mode 100644 index 00000000000..95d36355b29 --- /dev/null +++ b/tests/components/zwave_mqtt/__init__.py @@ -0,0 +1 @@ +"""Tests for the Z-Wave MQTT integration.""" diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/zwave_mqtt/common.py new file mode 100644 index 00000000000..ef85d2e5533 --- /dev/null +++ b/tests/components/zwave_mqtt/common.py @@ -0,0 +1,57 @@ +"""Helpers for tests.""" +import json + +from homeassistant import config_entries +from homeassistant.components.zwave_mqtt.const import DOMAIN + +from tests.async_mock import Mock, patch +from tests.common import MockConfigEntry + + +async def setup_zwave(hass, entry=None, fixture=None): + """Set up Z-Wave and load a dump.""" + hass.config.components.add("mqtt") + + if entry is None: + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + ) + + entry.add_to_hass(hass) + + with patch("homeassistant.components.mqtt.async_subscribe") as mock_subscribe: + mock_subscribe.return_value = Mock() + assert await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert "zwave_mqtt" in hass.config.components + assert len(mock_subscribe.mock_calls) == 1 + receive_message = mock_subscribe.mock_calls[0][1][2] + + if fixture is not None: + for line in fixture.split("\n"): + topic, payload = line.strip().split(",", 1) + receive_message(Mock(topic=topic, payload=payload)) + + await hass.async_block_till_done() + + return receive_message + + +class MQTTMessage: + """Represent a mock MQTT message.""" + + def __init__(self, topic, payload): + """Set up message.""" + self.topic = topic + self.payload = payload + + def decode(self): + """Decode message payload from a string to a json dict.""" + self.payload = json.loads(self.payload) + + def encode(self): + """Encode message payload into a string.""" + self.payload = json.dumps(self.payload) diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py new file mode 100644 index 00000000000..447348a4b6d --- /dev/null +++ b/tests/components/zwave_mqtt/conftest.py @@ -0,0 +1,40 @@ +"""Helpers for tests.""" +import json + +import pytest + +from .common import MQTTMessage + +from tests.async_mock import patch +from tests.common import load_fixture + + +@pytest.fixture(name="generic_data", scope="session") +def generic_data_fixture(): + """Load generic MQTT data and return it.""" + return load_fixture(f"zwave_mqtt/generic_network_dump.csv") + + +@pytest.fixture(name="sent_messages") +def sent_messages_fixture(): + """Fixture to capture sent messages.""" + sent_messages = [] + + with patch( + "homeassistant.components.mqtt.async_publish", + side_effect=lambda hass, topic, payload: sent_messages.append( + {"topic": topic, "payload": json.loads(payload)} + ), + ): + yield sent_messages + + +@pytest.fixture(name="switch_msg") +async def switch_msg_fixture(hass): + """Return a mock MQTT msg with a switch actuator message.""" + switch_json = json.loads( + await hass.async_add_executor_job(load_fixture, "zwave_mqtt/switch.json") + ) + message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"]) + message.encode() + return message diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/zwave_mqtt/test_config_flow.py new file mode 100644 index 00000000000..fbdf4012009 --- /dev/null +++ b/tests/components/zwave_mqtt/test_config_flow.py @@ -0,0 +1,53 @@ +"""Test the Z-Wave over MQTT config flow.""" +from homeassistant import config_entries, setup +from homeassistant.components.zwave_mqtt.config_flow import TITLE +from homeassistant.components.zwave_mqtt.const import DOMAIN + +from tests.async_mock import patch +from tests.common import MockConfigEntry + + +async def test_user_create_entry(hass): + """Test the user step creates an entry.""" + hass.config.components.add("mqtt") + 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"] is None + + with patch( + "homeassistant.components.zwave_mqtt.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.zwave_mqtt.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result2["type"] == "create_entry" + assert result2["title"] == TITLE + assert result2["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_mqtt_not_setup(hass): + """Test that mqtt is required.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "abort" + assert result["reason"] == "mqtt_required" + + +async def test_one_instance_allowed(hass): + """Test that only one instance is allowed.""" + entry = MockConfigEntry(domain=DOMAIN, data={}, title=TITLE) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "abort" + assert result["reason"] == "one_instance_allowed" diff --git a/tests/components/zwave_mqtt/test_init.py b/tests/components/zwave_mqtt/test_init.py new file mode 100644 index 00000000000..0b77905ab9b --- /dev/null +++ b/tests/components/zwave_mqtt/test_init.py @@ -0,0 +1,62 @@ +"""Test integration initialization.""" +from homeassistant import config_entries +from homeassistant.components.zwave_mqtt import DOMAIN, PLATFORMS, const + +from .common import setup_zwave + +from tests.common import MockConfigEntry + + +async def test_init_entry(hass, generic_data): + """Test setting up config entry.""" + await setup_zwave(hass, fixture=generic_data) + + # Verify integration + platform loaded. + assert "zwave_mqtt" in hass.config.components + for platform in PLATFORMS: + assert platform in hass.config.components, platform + assert f"{platform}.{DOMAIN}" in hass.config.components, f"{platform}.{DOMAIN}" + + # Verify services registered + assert hass.services.has_service(DOMAIN, const.SERVICE_ADD_NODE) + assert hass.services.has_service(DOMAIN, const.SERVICE_REMOVE_NODE) + + +async def test_unload_entry(hass, generic_data, switch_msg, caplog): + """Test unload the config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Z-Wave", + connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, + ) + entry.add_to_hass(hass) + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + + receive_message = await setup_zwave(hass, entry=entry, fixture=generic_data) + + assert entry.state == config_entries.ENTRY_STATE_LOADED + assert len(hass.states.async_entity_ids("switch")) == 1 + + await hass.config_entries.async_unload(entry.entry_id) + + assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED + assert len(hass.states.async_entity_ids("switch")) == 0 + + # Send a message for a switch from the broker to check that + # all entity topic subscribers are unsubscribed. + receive_message(switch_msg) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids("switch")) == 0 + + # Load the integration again and check that there are no errors when + # adding the entities. + # This asserts that we have unsubscribed the entity addition signals + # when unloading the integration previously. + await setup_zwave(hass, entry=entry, fixture=generic_data) + await hass.async_block_till_done() + + assert entry.state == config_entries.ENTRY_STATE_LOADED + assert len(hass.states.async_entity_ids("switch")) == 1 + for record in caplog.records: + assert record.levelname != "ERROR" diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/zwave_mqtt/test_scenes.py new file mode 100644 index 00000000000..10e1f94b229 --- /dev/null +++ b/tests/components/zwave_mqtt/test_scenes.py @@ -0,0 +1,88 @@ +"""Test Z-Wave (central) Scenes.""" +from .common import MQTTMessage, setup_zwave + +from tests.common import async_capture_events + + +async def test_scenes(hass, generic_data, sent_messages): + """Test setting up config entry.""" + + receive_message = await setup_zwave(hass, fixture=generic_data) + events = async_capture_events(hass, "zwave_mqtt.scene_activated") + + # Publish fake scene event on mqtt + message = MQTTMessage( + topic="OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/", + payload={ + "Label": "Scene", + "Value": 16, + "Units": "", + "Min": -2147483648, + "Max": 2147483647, + "Type": "Int", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", + "Index": 0, + "Node": 7, + "Genre": "User", + "Help": "", + "ValueIDKey": 122339347, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579630367, + }, + ) + message.encode() + receive_message(message) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 1 + assert events[0].data["scene_value_id"] == 16 + + # Publish fake central scene event on mqtt + message = MQTTMessage( + topic="OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/", + payload={ + "Label": "Scene 1", + "Value": { + "List": [ + {"Value": 0, "Label": "Inactive"}, + {"Value": 1, "Label": "Pressed 1 Time"}, + {"Value": 2, "Label": "Key Released"}, + {"Value": 3, "Label": "Key Held down"}, + ], + "Selected": "Pressed 1 Time", + "Selected_id": 1, + }, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "List", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", + "Index": 1, + "Node": 61, + "Genre": "User", + "Help": "", + "ValueIDKey": 281476005806100, + "ReadOnly": False, + "WriteOnly": False, + "ValueSet": False, + "ValuePolled": False, + "ChangeVerified": False, + "Event": "valueChanged", + "TimeStamp": 1579640710, + }, + ) + message.encode() + receive_message(message) + # wait for the event + await hass.async_block_till_done() + assert len(events) == 2 + assert events[1].data["scene_id"] == 1 + assert events[1].data["scene_label"] == "Scene 1" + assert events[1].data["scene_value_label"] == "Pressed 1 Time" diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/zwave_mqtt/test_switch.py new file mode 100644 index 00000000000..84929dabe0a --- /dev/null +++ b/tests/components/zwave_mqtt/test_switch.py @@ -0,0 +1,41 @@ +"""Test Z-Wave Switches.""" +from .common import setup_zwave + + +async def test_switch(hass, generic_data, sent_messages, switch_msg): + """Test setting up config entry.""" + receive_message = await setup_zwave(hass, fixture=generic_data) + + # Test loaded + state = hass.states.get("switch.smart_plug_switch") + assert state is not None + assert state.state == "off" + + # Test turning on + await hass.services.async_call( + "switch", "turn_on", {"entity_id": "switch.smart_plug_switch"}, blocking=True + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": True, "ValueIDKey": 541671440} + + # Feedback on state + switch_msg.decode() + switch_msg.payload["Value"] = True + switch_msg.encode() + receive_message(switch_msg) + await hass.async_block_till_done() + + state = hass.states.get("switch.smart_plug_switch") + assert state is not None + assert state.state == "on" + + # Test turning off + await hass.services.async_call( + "switch", "turn_off", {"entity_id": "switch.smart_plug_switch"}, blocking=True + ) + assert len(sent_messages) == 2 + msg = sent_messages[1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 541671440} diff --git a/tests/fixtures/zwave_mqtt/generic_network_dump.csv b/tests/fixtures/zwave_mqtt/generic_network_dump.csv new file mode 100644 index 00000000000..fb2aa1dcfe1 --- /dev/null +++ b/tests/fixtures/zwave_mqtt/generic_network_dump.csv @@ -0,0 +1,276 @@ +OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1008", "OZWDeamon_Version": "0.1", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueried", "TimeStamp": 1579566933, "ManufacturerSpecificDBReady": true, "homeID": 3245146787, "getControllerNodeId": 1, "getSUCNodeId": 1, "isPrimaryController": true, "isBridgeController": false, "hasExtendedTXStatistics": true, "getControllerLibraryVersion": "Z-Wave 3.95", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/zwave"} +OpenZWave/1/node/1/,{ "NodeID": 1, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": false, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0086:005A:0101", "ZWAProductURL": "", "ProductPic": "images/aeotec/zw090.png", "Description": "Aeotec Z-Stick Gen5 is a USB controller. When connected to a host controller via USB, it enables the host controller to take part in the Z-Wave network. Products that are Z-Wave certified can be used and communicate with other Z-Wave certified devices.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/1355/Z Stick Gen5 manual 1.pdf", "ProductPageURL": "", "InclusionHelp": "Plug the Z-Stick into USB port of your host Controller and then click the “Inclusion” button on your PC/host Controller application.", "ExclusionHelp": "Plug the Z-Stick into USB port of your host Controller and then click the “Exclusion” button on your PC/host Controller application.", "ResetHelp": "Use this procedure only in the event that the primary controller is missing or otherwise inoperable. Press and hold the Action Button on Z-Stick for 20 seconds and then release.", "WakeupHelp": "N/A", "ProductSupportURL": "", "Frequency": "", "Name": "Z-Stick Gen5", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAG4AAADICAIAAACGfENfAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nO19aXhcxZVo1V160Wrtthbb8iKBsORFsmzACwkMMA4JkyEkkEBwIJPwJR4eCUlMHrwM2UOACUMIIclkmzwyBBKME5YEs3jDNshgQ7AsL5IsedFuqVu997233o9Sl6pr6yu5BfN9j/Pp01f31KlT55w6dU5V3aUhQgi4A4QQhNBNLS4z9AqkmsYl/+mpIOsLAMB0l5EVdG9K9+Cybxnyfxq4FBI6jpOBAmbB3NNgMqUmboizooiCrUajiO1xgR8K4eCQhvx/GRMaSehl3QmrhMwZSdw0USglU03GVlMISo8hbRoaZKyFUsouXQog65QOam7YqqXF/0nXCkdmhDGYXt3L4T7MKbxVaBfCWZagaG1lAiv8iGEra6tgC0R+ljlWvg8uYUYi8f+fYBBTkllDUhKf8vipQZORucms+2iefEcMJS+iuorpRchZpo6Cv1B+niExxUTaYVgzUYkJOrJcxIuoAMdxEEKYFRNzhfLwJkApcJncM+LVoHCpSaRMIJcbA2GrjBsb27Z5gikB6ULXdZfEU62aKs/3Ju0kEon+/v5gIKClDMEPG9NE0zQGgxAqKCiYM2cOX/WegDFtvzgX6OzsfPPNN9/Yv980ffwiSbBwhVCDGkiXFEJYVDTr5ls+k5eX9+6KLwZB2gHpEVqRTJiGbigx0uPxFBUV5ebleUw/th4AAEAAAdS0yfBN3C1l1clxxxiv1080kWUJQiBEAi7tCFsJqxi2Gu0UGYMrYx33Hq3IwpM0QMwNcs4oiwZCEyiEFC6/3eRVYjQab8ga0CbmCaaUu2niCfXSKiHgQmE6W9JcEDF5YRTq0JfqhZHMeXlWBGOoPYUBNTFZ3DD/hQQ0csIEAPBLHiDZRwqlUojH0AiFpJtnrOK7e2/SjlBjJPEjKFkY85TvLRjChTuQhGFZIAfUpBCmHVnAJh05jkNCD28ykoscx8ELSXpZJ+OvkHl6VfRw8snNUAvBd0MrIGyVEdKdCEEINU0zTRObEiEUj8fxJZllhmHgKIkQSiaTpmkKGSoyuCLvTa+K75qN4sLJokAKU5uiP2FVaDzo9Xrwn2ka4VCIEDiOMzZ6VtOgpkFd0zQNjo+PC3dowuQzVSHppCwj43mmpR2h4Xmkm7RDlzOmHSyEz+8vLS3Tdd0wDCuZHBgYoH3fn5NbUlJqGIZpmgihwcFBrA/J5tPOnO51V1ASeG/SDgUQQhCLxUZGRkzTNE3Ttm3HtumsbSWTIyMjhmF4vV6QOshQLHTeKzBAepiXhWGCoWncBGM+bAEA4ORMQRBq48Gg4ziapmlQQwBpmobzDG4VCo07aCIpQQh1TWdsp96NMFXC3CLU1CX9ZNoBklAisy9tHaKJTAdpVErHV1ZVYyaQWq5DCAECuq7PqayiQ5imaRACujcipMKm6iyvMKiMIeAMapBuhH0wsgr7YAqy5mkEnMQ8zYRxQbpxAUgNg3hpJbtkJKFVlsUKNymU/i9IO4rNg5sAL9xIsDSikEcfZADiR3gXRMUNEhzIAExvWaZQU62UDN7ztDPhZZNipAsEJzZBqo3je68CAEC92+EzBk0GXOx2aBrJkHJmQCjduG7NJIt3VE9TvrfDyK9GajwjdUxkTK8GdcDSdT0V/QRL9/Q8AwFMQ6q7o6e/mxiakaHQJkyP0kcKZN0L465aRDc0GUF2mqnuRZElMPATziVDnjKbux2aRrHbIWU33CZFpyY+pU/mbJBR8ullLb7he5N2+C7p+SgsAx5DtX03hM4EbtMO8x9wU8MNpRpky9tUc6nFhBkSyDZayvszso2Nggmbdmih1XOBto57d+DDDWEFISTbRFlbvMNhaPgmNE/eZLJoqBg/tfvTAkykHWJ4RehlLhVqC/GAsiCLVa4ZFV3LCsyly1Gn26p3mTJWk/d21EnGTXZS0LMJR84ko868adzsW9wIydMzrdRKvRtph+wRiRXwM0MAAIAAAmmmoQtU9kbkFB3P9xQHaNsOo5ubR19mAjLfcRTGXQUl04rUktVPIpGIRCIIoVg0GoskvD6fz+czDdP0eDRN83g8hq4To0NNSyYStmMDABOJuG3blmUnk0krmYSaVlpaEg6HdV3zer30uZxMKv5SpqOCTIaExGWIwkxaIGXeNEya5jM4obQsKxwOnzlz5szpM5FoNBqJ6rpeOKuwsLAYnz+iFID0sGXbNqDSCC1SJBIOBALJZNy2bZ/Pm5uXt2DBgsrKSq/XQ0soFJIfb/UKRIZM60jmawrWMnoemUgkzp49e/z48WAgYNl2QUFhYUERywQbTsASAoAmt+L0eQdMO7DEY2Db9tDQYDgS8vu8paWldXV1efn5IP1pGUZ/SoQpOA2NpMtZfpINM00mk8FgsP3QoeD4uK7rFRUV9BpF2FC2znCTeWmnxhCPx/v6+jQI59fWLly4EN/JmOmskDkCugesRjAY3L9/fzQaraioME1T13WSNIBcH/4JlozWJ53SZewZuq5DCG3bPnv27NDQ0JIlS2prazHyXBRUw6RXug+0PJAYd/r06Xf+/veq6mpd14XPo/KZAdcytlZYkJFHvbjxeDw+n6+rqysnJ2fZsmXqgclC2mEkyxiGaUoiPUJoeHj46NGjixcvDoVCtm07jiOMHrQ+ZOHC3IklNHx0U5gScBEQQmgYRklJyZGOjvz8/Lr6eiAKJqRTWR5nqoSWMXhVCRe+V56AgOM4Rzo6mltaYrEY4HyNUY+f73yBvxSKxIwWPU5kBgSDwfMbGl7dvbuqujo3N5dxCJmapDthghKIl5W04zhO+6FDFbNnFxUV9ff3x+NxQEUuWizGZAopMaiXDQwlKeCwSAMAIBgMJhOJxqamGXrgOju7HQhhNBotKCiIRqPJZJIgCXNaepJweY8TziZmymNQLE1AaneE3R93bVlWQUFBR0cHeYAr65D2SIEww/ARhCcIBAKFs2bpuh4Oh2kyTdOYGUTW4UCUMegy3516ogndHKaOnfAU0TXNsizDkIY1Rms1krGMISSVmU+WdpLJpNfjAZKsRdMDiQNm1I3pnW/ChDZiR/zwDC4TIzK5BVBjQCYNXaXwM1Jgn85QhFWFYvhaqJgM4ybPuOmXNwr5j20nY8hbkL9005CA6jjDzaQTIoWO5tL7hCs7JikzTRjHJ6bUdR1vEOioIlONuVQgZQEqS4ds3DGEbISYuCw0DQHGqYUmkzXUNA2b0rIsJstlR2UO3B6yMRggMZbspAtDxoTDQDKZNAxjYGAAIFRZVSXjJpQNR0khWcZ8IsRkrJp8b4eZRMyw82QgfXhpL0Ock9LNIYTHjx379je/2dnZyfMZGhz81j337G9ri0ajN2/c+Pxzz/3LzTfftmnT22+9xffLsCXHwxiZMVMLwzftKHS4oKuEqmm0mZlJxCgplGOieYqePHIm7IxUJZPJ3bt2HenowJe//c1vHvqP/8AEJ06c2L1rVyAQONnbW19ff+idd6648sp/+fznjxw5QrqjR1poCJCKlWpTZjSrWnGmrJGQjNKBxgAq4grJaNbqvQRutXDRIr/fv2P7dnzZcfjwWwcP4tHeuWMHAOC8886rmTv36JEje/fsWdHc/MjDDzctXSqTQTjddF0nq1qFMGql+EsGSZezfG8HSw+5Y1BmtH0+34euuuqPTz75pyefvObaawnZ9lde2fbCC8tXrKiZOzeRSHzrO99xHAcB8M1vf3vOnDmM42QEMh/5OHgOKkoha/d2YIpYzY0w+fTGjYfeeednjz66e/fukeHhcCRy19e/3vb660XFxV++4w4IYVdn51e+/GW8DTVN86aNGz9+3XVAmW14jJCYR/I6CvVVI7Nzb2dgYCASidTU1IyPj4+Pj1uWhV9pQqKAS/jHYrHf/OpXzz37bCwWQwhpun7hhRd+cdOmitmzcQeWbT/60586tv2FTZvw4kbIh/FWjMnPz8/LywsGg5Zl2baNNzxdnZ0rW1t9Ph9jQUYdRl8Fku4xO/d2eFPSwytsQiCRSJzs7bUsa05lZUFBAbFLb2/vL3/xi67OThxbL73ssrXr1tFqqDNDYWFhbm5uIBCwbRtbk5jS7/cDF/7hBkmXM+92+P9CUoWx1JHBNM2FixbxNDk5OQ0NDQ0NDfiytKzMDWc3cZC0Eio1VSQpazAboFbSTXOeT2lp6cevu87r9W59+um2tjaSdhjD8ayYtMtDVlTmQWMyunBZoAahuPRI8nD06NF7v//9nhMn+KqBgYF7v//9gwcOYCb/93e/u/9HP1q6dOlLL77IO8U0pFI0pKvcW4MgJ70ScFsaSAUIuoqhB0D8UjJDQ2OikciL27Z1dHTwlF2dnS/87W/Dw8NYyvrzzvvmv/3bs88809DQANO9khcDupjdMjmZS5SeMIU0jGpZu7dDAA8Rb0G6vGjxYo/Hs3PHjsuvuIIZqp07dkAI6+rrIYRjY2Ob/vVfg+PjhmEYhoG4wK8QxqXkjMl4Muj63o4mc1ceqa4CkmMeYZOcnJzLr7zy9ddee+7ZZ2nOO3fseOnFF5ctX15TUzMyMnLjJz+58dOffu6ZZ2774hffaGsD8mxAM88ICn3pLtTq8wRZ2+0wXFD6ph5xfnrLZz/b/s47D/77v+/etau5pUXTtLcOHty7Z09hYeGX7rgDQtjf319UXPzFTZt+8uMf33vffRcsWUJPPZR+NMn0m0HUGdrtAEptoRww025nMmKK/IIeZBqfm5t7/49+9Iuf/Wzbtm37X38dAQAhbG5p2XTbbZWVlQghgNBAf/8D990XDod/8L3vffwTn/jIP/0TLQ+gVnxT0tmluXkyfrLTAgjuYzBEULJEp5H9/f2xWKy6uprZ7TBsiQQ0JhKJnOjutmy7uqqquKQEpHw5HA6T0yAAQFVlZcXs2UIhGePicnFxsd/vDwaD/BLd5/PRRmHkEVYJ/Yn2IZjNezvKWkVayM3NvWDJEiITIcjLy2tubmY0oZsLtaJB7X28BflLNw0JZN7tMBFKTCaZ18Je+bnDYJi2Mq3oVjLBFMsMYRlINGWSj5CJq7QzjTitXp3wZhKmDoYAcdtenvl7mXb4OMp3zMQRNyGfT2VSj5b0y8xr5tJN1paJ50ZHIYEiVgLycRwmnPO9TuYpYVSSaCKUDGM6Dh/+78ceg8ojd8dxTnR3M0hN11e2tn7+1ls9Ho+irSyi8ZIwju+GCaGkh1b63g6PEcoKJMYlyEAggI/OIpFITk4ORkYikd/91389+vOfFxYW8s2JfMFgcHlTE89865YtPq/3c7feyusz1YURI7DCrLxqTDnL93YIkNq/Pv88vnjl5ZfHg0GMbD906PLLLy8sLOR5vrht29tvvbVj+/ZwOIwZYQqQ/rd3zx5eH8W8EUJGpfhLBkmXZ2q3g9lallVcXIxn8apVqw53dLS2tgIAEomEPydH2PW37rnHMAzDNH/929/m5+cDCBEABfn5psdTW1tr2/bBAwcQAIlEQiBD+sNvUlFnOu3wq1+hlHytYk2n63o8FsPl0dHR/Px8uiHdF+l92fLlyUQCAeD3+QhNXX3952+9te/MmZLS0k1f+AKz9kLpsT4j8GTCrEJryhAIrZGWdhjT8CsMfmky2bdk9ZOXn//X558vKCjo6ur61A038ExA+mAc6ei49LLLPnbttcUlJcFgEEKIAHhj//6v3HFHMBDwpm7LCKWa6gTndVdbTWYfXNZ4FN1MMc78ooSvtW171qxZS5ctKy4u/vCHP3z82DG1nhDCr9911/ZXXvmHSy/t7+ubQAJQX19/191333vffTfceCPfNQlYLkGYTt1gZEgMM3VvZ4LecYaHh0tKShzHSSSTwWBQxp/A177ylfPOP//Gm24qnDUrmUxizmfPnsWPGvzjhg18k6nGPtJcKMBUkaQ8U/d2ZHiehoH/9aUvFRYWfu873wmMjWEiBKFt26FQKBQK3ffDH2JkRj1JFy77PXdQHbJBLlbyMk2EFa5qogmE7e3tZ86cSSYSPr9/aephFZoGpkele3/wg3nz5jU2NZVXVIRCIVw1MjKy/ZVXAAAf/ed/phftMP0Eftpph1aHL6dpKkeyn01msiFKX/3SgywUiHYQCKFlWStWrLh4zRqeP2NEwuHHDz+8bdu2vz7//MDAAH4xBAFwXn39zZ/9LADAcZynt2yhZygvWEaTKSxILmXuJWwCyG5H1gGQTBCaQNaE1Pb29paljh3nzpuHb+crYPNXv7po8eJbbrmloKBg4rVbAI4cOfJ/7rqLrCXpwQCiYcag8D6GRqim0NkZSvoym/d2GDUw3rasRDKJ/5hUy/MEANx4001Dg4Nbt27FL//g6rr6+vsfeOBTN9ygaRqgmNDzUWg1ISj0pXmq1ecJsrnbwXzxM2yE7fza2pKSkkQ8PnfePE3TxsfHc3NzFfon4nHTNHNycyceLoQQAKBpWigcblq6tGL27Afuv5+oSgpT0iJbKjMw+XEcJDk0o6sUZAToYfT5fKFQ6PixYz6/f+/evR//xCeOHzs2b/58mpKZbk888UR5efmFF13k9Xoty8LYw+3tX9+8WSgYmLppFMLzzOnLDGmHRFlaILVlAWdfgJBwt4O9dN369Qghn8/X29PDi8K02nznnV1dXdXV1RCAvLy8L2za9JOHHwYAQNwF+U/ZZRpexiRrwB0n0oOkMD0dtbN8bwelp1THcQzTxMSzCgsDgcDY2Bh+0V3Y9oH773/0kUdw8/9+7LHHHn/8i5s2PfmHPwwNDU0MFfWfnk+0VARkktPyKwzqsiEBNu3wYZXoCdLjLh+nmZ4QQrquj46O2raNENq/f//CRYsaGxvJS5AMz8HBwZ8/+ujPf/nLxsbGhx95pKOj489bt3q93ovXrBGcs6V7xDTmOKMmo44MSZuFIZjZezsIoZaVK5/6058AQvXnnQcAcByHnH5DCLc89dScOXM6OzuXLFniOI5l2xc0NPzk0UdLS0u9Xm9vby+EsKi4mJnDwh2B+yX6zKYd0gcvDb8HIGYiVbLdTjKZ7OnuXrV6NUDIdpzenh7btgtnzSJkp06e7O3pSSQSo6OjGz/zmcKCgmeeeebhhx766ubNCICWlhaE0MneXnWmQNQ+wo3ObnQUEsjWmBiZ9t4OiT58uGFWxW6AUCYSCbK6DgQCNs7LAAAAmltaXt29+4orr3zqj3/Udf2H99//0IMPBgKBb9x99zXXXHPJJZcMDQ29uns3s21mLKKI9W4kVKgmCx1CGWb83k4kGg2HQjhuGoZRXlGRpEzZumrV2bNnY7HYytbWO7/2tXvvu2/nq68ebm+vrKqqqalxHOfrmzdHo1HhET3vPucyc3mzymaCbORm9t6OYRh+v394eHhkZOT48eN5eXnBQIB4KLbv/7777js3b77t9ts9Hs9VGzb84fHH8/Lza2pqAADhcHj7yy8Lco5kiUZEomVQmC+jUlNKO1k7ZOMdB0Koadqll13W3NIyNDjo9XrLKyri8XhxcTFN84EPfvD666//9A03XP/JT977wx8ODgy8tm/f5ISAEEEI0v+Q8uhMYTuGMruQhXs7EyMsGnDbtvft3ZtMJsvKyj542WUAgGXLl5O2pK/Pfu5z82trv3z77dU1NWvXrcNncbQ8PH+hgQA3RYSWdZlVaE0ZAqE10n7mjemeH2dyybaSnERpmmaYJoSQ/F5jb28vAMDr9YbDYbqjf7j88hdeeunmW24JjI0dPXKErsKeSAPmIJy8QkVkwOvOI3kmjH0mlVV3r4g1QjmYWk3TVq9evW79er/fv3vXrmg0Gg6FwqFQwwUXbHvhheHhYbqJx+P54KWXfu3OO6//1KfSWImix5q1a3mxM9qOECsMpMCou8jSvR1JW9L9qtWr8XOO5eXlPr/f6/XefMstd3zpS/hEUgaO48ydN2+SDwAAAF3XV7a2fnrjRpByfGGnQJ52mAjDt5oSkpSzcMiG5RWuV2gwDEPX9eqaGny5uK7uG/fcw1OSYMQgmQku7MvlSJ+7ykLIwr0dckFXyeK3IF8pHYdhos6HLsGNOpDb2PB5iUGK34TnjUhXCV1DKJmwwHRH4/nmNJJMWCGfafuasF/h5JA1AWS3o6BWyKcWnW+uNp9La2ZEZvBQ0fDIdBHyFPaLQZB2FDNuemmHEYhXOBIO809jKeaUkLOrmZ4pewAuKdHldyPtMOWMywi6/Mcnnjhy5MhXN2/2pL6fBdJdT+0XIN213Qz2TKUd4Wqe6RhlvLeTybUVSq675BKPx6OnvjpNmvMWJF9Xc8lZAKIdkYRw6mmHRFlFXGP6EBBQpwl8cuA50MiDBw7ohsHjaWLywT8ayacvBgQmk5zL8boTFdykBEDSjmzqKaRkuMhqZcmHLiSTSZkAdKZ20x2YSjSnTSa7dNOQQJbe23GH5KfJ6dOnKyoqRkdH+SaEWMGQkMlUkAEf35lLBVKWl96993ZoPBmeOXPm6LpOfjuYiEX7o0uewI1LntsKVA1Zem+HewMf0/Nj++K2bYl4HEK4Zu3awlmz4rHYq7t3L66rA+kWpHunm/Oy8XiM1DQNb/BJ7Mb/8XvlGXUUEvBphxYsa+/t0FiGG536L/nAB7q7urw+X35+PoSwq7u7vLw8GAjwhuO5CRVmanl1FPIzMYQ3oiwsEkp6+LPwTTYFMK4KIbRt+4033nj9tddw1h4eGlq3fj3+AWYmyQizDY1UJ6upAsPTpeJ0OQu/t8ODjKdt26/t2zdnzpyy8nJcZdv2ju3bT508uWr1apR+TC2TCnBDTnuTzAMUudFNVdbSjhoUIYxhrut6S0vLrl27Fi1ahKs+eOmlIyMjF118MfOjGcCFc9HTUx1SQfrknaG0k/ZxHJAepJklt7BM0o66CQbbtnfu3Hns6NH2Q4cIzalTp17atg0/DEOriuRAm4zvLqPOQoZqrRkCYb9Ze28HUpRMtCaXuq5fvGbNiuZm+iOdx48dq6ysZIIgSl8nMIZgZOCRQvPxSF53XjVZLgJc0Myw2+F7Yihlogs7SyaTv/31rwGEGzZsKCsrAwDE4/GL16yJRaM8W6E8amn5yU5X8UMrYyvDyJAYMjzJxv+XTTeQHq2E9IZh3HjTTXNrak50d2PM6Ojo0NDQqVOnUPq0jcVivb29J0+elM07wE1ngUgU8JRCpaaKpMtZeKQAgImb1MKBpcGyrMd//3uP11teUYEJTNMsKS5esHAh+XFw/L+/r2/u3LmBQCBALTnpHgmGKdNIRhKZVNmC7N3bkbgADYZhLGlsrK+vj0SjCCEI4djY2J5XXzUM42PXXuvz+cggG4YRi8V8Pt/Zs2fJu86MYIqueWsyXplRHUY16OYBapAemNC5vbcjlIwwjMViiUTi0KFDZ86cmTdvHgBg4cKF48FgX18f/dAlQshBCN/s9Xo8ULLogVx2QqKVaUYQGkjmXsImgKQdWQdqgdxUMZr7/f4LL7ooFoutWr2aVA0PDzu27dg2mePqvoRlWlohE8ilHQwKu9M+JNOavszmvZ2MrCzLeujBB/1+/8Vr1jS3tEw4oON0nzgBlCsB2bwD6b4vNBYvCcNBdsk3UTREWd/t0H6BuIWUaZq33X77i9u2rWhuxlUHDxyAEJ5//vk0hzSJOQfnmQsnAQNqQ2cFZuTejnBgMfj9/o9cfXX7oUP4i1eNTU0D/f2RSIR8BVzmm7zrKbx4emmH4TDltEOirDCiCy0rJIDyQy1C6TjOn7duhRCSr81qmvb2228jhGoXLDBTr6XIIr2MM5BEALWtGSdlODAJU8GHFLL/ezt4kUXTkP+2bQfGxizLIlZ7+623IIT4S5+yjtQK8MIIk48wyALR7lasnbwhgSy8twOA9A0Jhtjj8Wy8+eaWlSuXLluGyZavWGEYBv3xb8UEp2M/818oFZCHGl5Nnr8QyUhCl7Nwbwch9uFwJBpbhFAikXju2Wcdxzl16tTcuXMxQWBsLCc3F1DehBCaDBepiebGARG38uXDQkZ1pg0z8k02mWeZprnhQx868Oab82trCSsz/XtgwrZCiyi6nmra4XUUEsjWmIhOO/T851ewdAfCIRVajk/6iUTi8d//PmlZo6OjlZWVmKyurs52HJLBFWmHCMDrgxSrCxcGktHIwiKhRNQ2LAvv7UxciqqYtGOaZuGsWYZhFBUVEeLD7e29vb11dXXqz9XxbGXCTG/+MrFCNiSKvrJ3b0e+PacnQk5OTk5OTmBsDH80BwBw8dq1c3t7iR0VaUemIT/r1RPcjVJMFZ98AGefbO52SEaTeY1hGJdfcUUoFMrNzSU/U7J3z57+vr6ly5Zha7qZ4HTXwqWPorkb4unBxI95MeYAlF2AfEzohnxbtidNc2y758QJ/FEx0mRla+uChQvxShNk8kq6X0ZOulM3aScjH159YV+kSvAL6HS8cBuVlPsBQmbZdldXF0Lo2NGjBNnd1YU/3gJTIOPDlMl/PszJhBHyFEZ2ISumR5CelsUfNRWGA4U0IH0BwFsEX/p8vtLS0qe3bFnZ2oqRoVAoHA53dXW5+XVkxoJAaQWxnKkyr9e5JCsMBjNtAWdH4exmKBnuzESj+1u6bFkymSwvL8fNd+3cefr06cbGRpc/yebGCijTN4eECjK1fDSTIUl5Bh8pEEI4HM7JzbVtG6edf9ywwbKs/fv30781rR4hNxFA0Twj5bRB8Hs7uEIWcfkqwgt/YchxHD4wY8pYLPbnrVtjsdgLf/sbSq1bTdO88MILsVfyrejmjCTCLMGrICvzxAq2QoPQDBG+40hQ/ExhqqQhSSQ0T+bxeIqKik6dPDkntdXp7u4GAPT09KBMyxqZAEzakSUuhWrk0uW6SsgHkifZhJlakU8z8uXTDoTQcZxwKBQMBGpqajDBkY4OAEDn8eP4My8ZJylIN5kwubkBRdJnrOxmYDCc070dISV5RhR32dPTM9Dfn0gk1q1f7zhOKByuqakZHBjAW52mpqan/vSnygYCX4EAAAw4SURBVMpKj8cjm9p0X1ByoMAIw99xE0rOKKUwhYKe/M/OIwUM0FW7du4cHhrCAdQ0zfr6+lgsVjhrFiaoqq6+5mMf83q9+OF+xTwgVXSB7iujSLx42YXJb2fw4+B+TOgmTDi/7vrrlzQ1lZWXAwBisdjAwMDFa9a0vf46pnlt376tTz/d09Mj+010BXOhADy9sIphyCNpCwgTDs9QfG+HH0k1YK7Y9ZiGhmG8tm8fvinm8XiqqqrefOONdevW4X5bV61CCB14802yPGKkpIVByg9/0jJD7nAMujvpIZS8IkJ6Whh2jyGTksghI1BYffHixZFIBE+8WCwWDAbb29sxq9dfe+2Zv/zl5MmTbn6AGqbnCpk8Qgn5WMmEAlImRuTHQyYSBreHbDKBaK48Hvf0xv79ZWVlaOVKB6HDhw9XV1efd/75mKZ11SrHceKx2DR2OzJViZnUs0qhuExTYYgj5Rn5OA6jSWVVFUYZhnH11VefPnVq7549hObpp5766SOPJBIJtf6THXHexEoiOZqRNckWCD6OQxcU/wETgCRe2X7oUFV1dd+ZMwgh27bb2trWrF1LvBIA0LR06fza2mkshoSSQEkwZTwIirxboSODJA1pZNpTjXxu4oEfeQDSvk7LND99+nRxUZFhGBBCy7IGBwfnzpu3v60Nj2Q4HD58+HB/fz/2Si0FSBkTiSPwVbS9ZD/3rsDQzWUEdNc0UlO3VHQmU4Dpr6SkpK2tzev14tFbsGDByy+9dPjwYUzW09NTVFTkz8khR78Ze6QtyBiUH2aZW8jio6J3hUgYxGlHtnRSO6xQ1hXNzc0tLdhnfT7fqtWr/X5/PB7HBA0NDU9v2dJ+6NCaNWvoV+tlDJnJKFTJvUVkavKXbtJO9j6OAyE5EyIK4/L2V14Jh8Mf/shHIIQej6ejo8O27SWpHxxsbW1NJpM4AhCekCRiPN9F0Y2Z8nSZcVhG83NXWQhpD1DTZbpjPiQDZgDli6SdO3YMDw/n5ubitLNv716EUGlZGRnhOZWVCxYsoM8rhSBb9DDLQCDyWUVDWkeaRph2GD50jwCbEkhGWBZ0FRLwxOvWr//722/j/K7r+gVLlvT19ZWVluLatra2gf5+hFBzSwtIH07Z/GWQjD5EqkgkEo/H1cPDpHveanzqYzyJXELZA9TMgkMtDaMY0wRCODQ0FI1Gly5dCgDweDxPP/XUmrVr161fDwBYnfrAGHuWI0qAQmGwPzJ2P3b0aHd399nR0abGxgULF5KkR7OSKTIloFtpxLoYgDLWIgqYVjTQ+O7ubtMwyNsPOTk5zS0ts2fPJhy2btmyY/t22dk7zZMRQEbpOE5bWxv+8tvWP//5+PHjsqApU4qpUiDpctZ++ICoAdLHau7cuchxsOy2ZT3zl7+MjY0lk8m6+npMWVxSEgqFQPpUhZLJJcSg9J2i4zjRWCwcDo+MjCSTSbxaID9nDKbrgBkhC/d2aA15r4mEwwcPHhwZHgYAGKa54aqrPB6PYUw+q6RpGn4ZPM3X5PmXNgf+r1Gg67rP57vyyitHRkai0Whzc3NtbS1jPt6neMl5m2TU2lAEEbpMhyp+VIUzDZMdO3asuLi4p6enddUqCKHP56uYPbt2wQJy43tsbAz/DjZZpSuAdE2/9IzjLJ3E582b99GPfhT/CGdBQYFFfaiZV41cKpK1DBCTdjJmapdAXkSm2w4NDubl58+fPx8rHw6FAEJHOjoWLVqECRqbmmLRKPMYGzGNBiFZVxI8S5MyKy5ACE3TrKqqCgaDBEPSrtBqtPlkYSQjMvv3dgA1yACADVddNTQ0hL/ZAgDIzcsrKy+vmD2bEL/R1jYwMLBo8WL6tyXIEh2kLMVMCJgODCbjsx78nBWqIwt3QmSWHingpKHZ4o+AQggty/r+d787e/bsla2tVVVVuPbCiy7q7OzE65XJtsRf0u950V5JTEy7Hm1K7IO8gjObdsg1E4ZppGJMCD1/+av//M+x0dG/v/02AMA0zbu/8Y28/PxTp07hLNFx+PCuXbv6+vq0dMBLa4QQ1DRInRgRS2kUnhR0Xccfstd1Hb8qwAccWnImvTBaqBMOb7TJb7LxZj6X0ZvMAPPnDw4O7tu7F0Jo2/Z3v/3tUChUW1uLjWJZFoTQY5q0HWkf1Cjb0fYiBRpoc9NfkHApKn0pHAOePi0zK4aOYITTlg7b3V1duXl5AIBQKIRXcJDK+H6/37IsfGABU4seekt3dmSkuKSEDnAnurv9OTn9/f15eXl1dXVk5mKe/Izmg2YgEIjFYoZhaJrmOA520s7OzosuuogsxRg1hZryaYpviMtZurdDOQLj5h6PB0JI3nmidSZQWlYG07MKiZXkG4G0pUAqgBIMk2ds204mk8QKQudSKC7TVJGmspd2Ugo4jkM0Rwg5jkOcFIhWhanvG0wmaHqoSRzknZFmQqd47PKxWIyOlQycu8pCyMK9HYSQY08A4/MgdXOcscIEIAAZJDEoUZjzTY36IChpCCEke1bbtqPRqG3beGqTsXQcBzkOSA9NjOIyHaFo9c4gs3BvByHk9fnw75UwA44vycRnawHQISR/GoQQpGjSYx+9t2H0x/+JHS3LGh8fj8Vi5MYOHmzMjYmMCvdUpx0eD7NybwdCWFpSMjY6yowYKWPX4KWB6XeONQg1bfIDqhMicbGSFhhQj3M6joOfV4jFYrqu4xhN1zqOoxsGWWbxGiHRIlQNNH127u14fT5N1xFCpmniiEniFJ1A8Qpm0jQTQXLCRvgYQyY05oZnK+YJqAiD8wx+stDn8xEvxmEH0/f39dWkfitArSZ/+e6lHQihP6WAx+OJxWLYcCgF2JrYCvQ6EKRPWAAFJyPMwR02nEMBrsWeiIcKm9iyLMuycApyHGcsEFi2fDlQTrVzgSzc28GXCxctOnDgQEFBga7rXq8X/2Itmap4rhmGgd0EzzJN00xNx1aYCIu6DrV0MVIeTcxK+7thGF6v1zRN/nDIsizcF7Zjd1dXU1PT5Cbqf+a9HWLZhvPP//s77xQWFpqm6fV6HcehV5qAnOsZBrm/iABEANh26hDMmnjK0rZtHOws204kEmRVhDeF9G6HGXXs/tgZ4/E4Lp88ebKquhq/lsHbi9ZXmNxl+gIqvMIs3tsBAHi83uXLl7e3t4dDIa/P5/F48A8MEVfCTwPjiDax0YYAAKBrOq0PhBDfw4AQGrqO3+IjcZbxBVK2LCuRSCSTSfwDfdgfQ6HQ8NDQksbG8vJyxtEUirgHQdoRhl4+1sqiMuGraVpjY+PQ0NDg4GA4FMKGwyFM13WSx3HcxDdsyayE1CEuOZXQDQM3JzSAOnTA9sIWtCiwbXt4eBgCUDF7Nn4pSJj9FUoJM8yMpx3GmgCA8vLy8vLy8fHxvjNnkolEPB7Pzc3Fj0ibpkkCFn5OiExY2pTYHBBCK5mMRqNESBwxSB7DYRSXk8mkbduRSCQWixUWFFxwwQX4VWlh7JoJSNvtMPlHGHH5LCREQgjz8vLwp6WDwWB/fz8AIBqJJAzD5/NN/Bw9hPh70Db16Stsymgshu9ix2KxsbEx0jsJFzilgNRudWxszOfz5ebmzp8/v4x6XoHPn8IykJ+NyRQE1MBMpB1ZBwx3OuLywVg9XAUFBQUFBZjh2NjY2ZERAEA8Hk/E4/gWq+M4iUSCfv46HApFIhHHcSLRKDltwusBTdNs2x4bHTVM0+fzeb1ev9/f0NBAP3/N50bmUjipFclaBmzayZippw28gxcVFdGfKBgdHQ0EArZta+T4AyGEH3pBKD8/P5lIAIQMXZ+Y/pqmQViQn19fX49tJxOeSc1CAtocwggIuJFQIZkt3fswbZiCM78PasjCvR3ZmklIqaAXXspWHrJWil5cUtIYoagMT1LOwr0d91FVmLVojCKhKbIBv9xRRElh4OMvmRwto6e5Ze29HTe1wrypBnrBkVE2hQyM/GgG3tt5P+1kDd5PO1kDwQ8f0AXF/2mkHQYpZMLjGQmZtjxSBkIV1PoqWvHILNzbmfZiXsiE5k9vJRVtGbyClUsmIFPaEcqT5fd23IPLwZhSL+pdDQ0y5FSVej/tzAi8n3ayBlm7t8PT0JTCQ62MPEH6TOSRwrZqmYXHP3wcIBjFcREjT5a+yeYOFLlCQaMWg2k7jdgqTFNCYfg0SA/q/wMVWYIUr49S/AAAAABJRU5ErkJggg==" }, "Event": "nodeQueriesComplete", "TimeStamp": 1579566911, "NodeManufacturerName": "AEON Labs", "NodeProductName": "ZW090 Z-Stick Gen5 US", "NodeBasicString": "Static Controller", "NodeBasic": 2, "NodeGenericString": "Static Controller", "NodeGeneric": 2, "NodeSpecificString": "Static PC Controller", "NodeSpecific": 1, "NodeManufacturerID": "0x0086", "NodeProductType": "0x0101", "NodeProductID": "0x005a", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 0, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "Unknown Type (0x0000)", "NodeDeviceType": 0, "NodeRole": 0, "NodeRoleString": "Central Controller", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 31, 32, 33, 36, 37, 39 ]} +OpenZWave/1/node/1/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/32/value/17301521/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 1, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 17301521, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/22799473140563988/,{ "Label": "LED indicator configuration", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Enable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 81, "Node": 1, "Genre": "Config", "Help": "Enable/Disable LED indicator when plugged in", "ValueIDKey": 22799473140563988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/61924494903345172/,{ "Label": "Configuration of the RF power level", "Value": { "List": [ { "Value": 1, "Label": "1" }, { "Value": 2, "Label": "2" }, { "Value": 3, "Label": "3" }, { "Value": 4, "Label": "4" }, { "Value": 5, "Label": "5" }, { "Value": 6, "Label": "6" }, { "Value": 7, "Label": "7" }, { "Value": 8, "Label": "8" }, { "Value": 9, "Label": "9" }, { "Value": 10, "Label": "10" } ], "Selected": "10" }, "Units": "", "Min": 1, "Max": 10, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 220, "Node": 1, "Genre": "Config", "Help": "1~10, other= ignore. A total of 10 levels, level 1 as the weak output power, and so on, 10 for most output power level", "ValueIDKey": 61924494903345172, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/68116944390979604/,{ "Label": "Security network enabled", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 242, "Node": 1, "Genre": "Config", "Help": "", "ValueIDKey": 68116944390979604, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/68398419367690259/,{ "Label": "Security network key", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 243, "Node": 1, "Genre": "Config", "Help": "", "ValueIDKey": 68398419367690259, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/70931694158086164/,{ "Label": "Lock/Unlock Configuration", "Value": { "List": [ { "Value": 0, "Label": "Unlock" }, { "Value": 1, "Label": "Lock" } ], "Selected": "Unlock" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 252, "Node": 1, "Genre": "Config", "Help": "Lock/ unlock all configuration parameters", "ValueIDKey": 70931694158086164, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/112/value/71776119088218131/,{ "Label": "Reset default configuration", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 255, "Node": 1, "Genre": "Config", "Help": "Reset to the default configuration", "ValueIDKey": 71776119088218131, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/31227923/,{ "Label": "Loaded Config Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 1, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 31227923, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/281475007938579/,{ "Label": "Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 1, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475007938579, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/562949984649235/,{ "Label": "Latest Available Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 1, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562949984649235, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/844424961359895/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 1, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844424961359895, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/1/instance/1/commandclass/114/value/1125899938070551/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 1, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125899938070551, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/,{ "NodeID": 32, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0208:0005:0101", "ZWAProductURL": "", "ProductPic": "images/hank/hkzw-so01-smartplug.png", "Description": "Smart Plug is a Z-Wave Switch plugin module specifically used to enable Z-Wave command and control (on/off) of any plug-in tool. It can report wattage consumption or kWh energy usage.Smart Plug is also a security Z-Wave device and supports the Over The Air (OTA) feature for the product’s firmware upgrade.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/1789/HKZW-SO01_manual.pdf", "ProductPageURL": "", "InclusionHelp": "Set the Z-Wave network main controller into learning mode. Short press the Z-button, the LED will keep turning on or off, which indicates the inclusion is successful.", "ExclusionHelp": "Set the Z-Wave network main controller into remove mode. Triple click the Z-button, the LED will blink slowly, which indicates the inclusion is successful.", "ResetHelp": "Press and hold the Z-button for more than 20 seconds, the LED will blink faster and faster when the button is pressed. If holding time more than 20seconds, the LED indicator will be on for 3 seconds then it blinking slowly, which indicates the reseting is successful. Use this procedure only in the event that the network primary controller is missing or otherwise inoperable.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "Smart Plug", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAJwAAADICAIAAACSxR/7AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nNV9WY8c17HmyaysvXc2ySavJEoUpbEWyL42BAMDPxjzNhj4yU8DzO+6mJ/gR/+EWe4dDGyPLEsCLAsQTbJJmmSL7K7u2iuzch6CHR0ZW56qbmow8dDIOnlOnFi/iMzOykrKsgwhwN8kScqyTJIkGARnrTnIhE62mLAJkrMjieSgLpQcYhY6I5G7+HZzZAADShtSwzIZgua1ZLlcqieYTIwpY6cOWspYhkCVmGK+BS22LMJ8cpwXM99RylGhdn7QHCm54SDOee1URnSBXEynUXfKU+xA8nHWSuZysnp2vWlMZUcAP4YcaaU1gmFkRzZfU6DMB5wL51eFoCPsrBxnqkohFADRZHAUZspbAc42kuFPZZYLVUMxqSwgDQI81DiQlsG9ZKapKocQMqmVFMXLdBdmKVsnp6VwUh4LrCzQtoQJRobJjaTAavCp8sgolwGBgaIqEkR80GMrD+EgZVzUPGMTVOMGLSqpV6wwZCPMiDK8ZORZ2U8lcU45qe+gHA6ykFW9ZcUWU81SSk0SxrliGQtVVPPV1hhnXAUoOsjqGVvLtLL0lKkWs9BKJifdpWySVUyuS26OSVVRpSQ8BmMMoY77UOn4qXbHWstaAKsulJ6Ta1ERR4XaWhBpxlqBfVIXZhJYZNmw4o6OMDiifKyqIAdZnqk6sGkOcjAJLQUdRdhauFKAkeVyuVgslstlkiRpmiZJslwui6Ioy7Ldbqdpulgsms1mlmUqzqlSqWoGrbhaSr2ez3zANlYN4eCbSrXcnLM/MkExyvO8KIrFYrFYLPI8n0wmRVE0m82NjY1ms4kyZ1mWpilGAA2FRqORZdmzZ89ms9nNmzf7/f6PqRGvqUFkg5U6VlVja729RXG1UEFWGlnyVeiz0iKcpx2VP8/zPM8pw+VyWZZlmqZpmgYtmaRGKFuWZa1W6/nz5ycnJ2maXr9+fWdnB3KaaqHKaRV+mXJqgtXYXepg8XLEkqd8VJAKW9PoWeo5ekATCEeoR0tCwfVW0OJDHYGDbre7WCyGw+F8Pj87OyuKYmtr6/r1661WSzUItRUzAtOLbq0sV3WQrGvdwGZa8rHgCJr/2LGV9Kpj6I7oPOawyDhWyYEf6aROp1MUxWg0gk0Xi8V4PB6Px71e78aNGw4mWzaxPjLLe/CrcrHIn6aihDqNHsvMCyFAMxKqzkOorJXzDREDUhhpt9t5ng+Hw+ScyrIsimI6nQ4Gg3a7vbu7u7e312g0kI+VMBap8y/u/aqFs1YTK41qecrUkR5F2Wiq/ciei7eyxCdw6tnZGUyAggqniqKYzWaj0WixWOzs7Ny4cQNasKD18zGDFUkcG1lZxZKd6Q8H8/l8Pp/n55QkSavVArmh74DwLAX5u/siWYo4BNchi8ViPp8vzml5Tnmez+fz5XK5u7t7584ddQvL3OHcqcPhkOqVnF8Cwe55no/H49PT062trb29ve3t7fUUofsmcKVFTwSSguo4DhZFkec5HUFZ4dRyuWy1Wo1Gg0IlmxyJ2+sRpDu4bTabTSaT6XQKFyrL5bLRaECEQQJlWZZlWbPZbDQa7BJzOp02Go2dnZ0g8kPCFY602+2iKE5PTyViwY7QVIOEk8lkOBymabq/v7+/v49wLRtGtaYGeUdJNbF6DGZaLBbMcFaeMSSwijzzhO9vXAjeAjDAA4gnzLlGowFOarfb7XYbggyvUlTBqN2TJIEqvlwuNzc3qduCSE06iE49OztzihSGFKgzn8+Hw+F4PN7b27t9+zb0yapNqPXYvpVbz05q4imIKb+2ST4qKzmHrYVTi8ViMplMJpPZbAauKsW1I7gNCZIMWdG+gRa2pEr0IpLSdDqFTEWnMqWkWWWm+q0A7g7RM5/PJ5NJkiRvv/02+hXNonqXns2Clj10hJ2dTCaA2GzcklUVIpyXE8gqzC0sbIDqUHoBElutVrfb3dzchPs46JVQzZJAkI05LGjpGE9qgNK/9CMzoJOpuLAoinDecHQ6HajHp6en165dU93BDmjCZOyzj3vggEQUFaon0384HGKS0QIczhMoTdNWqwWeQ59hFqJsSLgjcxs9deXE/OGnnTqhdgkQPl2Upmmz2SzLMs9zWt0tohN4psoZKFNyXl3UDVhpSZIkz/P79++XZdlut+HGaZZl0DRRDGSeo1lI3WYJ9qMRlUEtT2oqh2h3sml5noP68I8BOs2yP45nsluR7kFUUeWTGsLgcDjc3Nzs9Xo00FRsZLkut47xpaWtfyqEsMzz0cnJ2csf8rOz5WSymIzPhsNP/+N/anW7qpqsH5SD6ta1rmX2D+dZC7ejGSvpLAryGTWolbIxNpXxm2VZ+5wc0dnWMsb9iiA5x5yaDYf/+7/+y+L7B63ng97LRX/UaE6TycnpdH52tjw+6c7/3b//VbPTuQwwSLtHgjbbFHKJ1RdmMTqSQKOEa+jiWiMyBaTEuAdVj0YWDqroynaPFEYCj0rL+Xz8xf9pPXyajfIwCYvQmoZytJHnm63Z1kHjxl7SbMbEkGzd5QizZyQaU5Sykk0mA3zM5Gb0QCKhSk73G4wAZMuZDhieUowQ4TnfH2VZpt1u+ze/ycqyv7OztXetu73T3ths9Xppq9nIsrSRNUgZC1XLoniOHRhE17ozMp1UUwQBb5VqR4WWSaOGDCOmsFp+QtXNMqSoXxlzJo8qgLSLVKfd7f6H//xfgognpw3xFXcoEZ2BhWrqjqz/ogcqsCVJov/vlxqacffVc9yARCcjScVYJDEhpcx0kJnPiUW2i+XR+ErEBAhVA8aU1aTandSijuSpX5LLTAp1KF9Lakw4rKR6dJWc79ceyna9tFNTxyELcv3lDC2ktDEGTFWERGuyKJM5sRL5GrKsYiElbWqluCQK5mtEpF+/2Uwpm4MBkkq3fVHdIedU7nbKdoBtUJsulhByPtXfr9nS2eu5Zw10oQKoFmQ8VVxZiawAAhfSEAFSsyKjQ4gYUlankvsiyskUl+SO6ngpOh1UMj5Za6X11zoBp46vV6GsCsLAnH6UkZSyxWpuWc6uFVEWAHUORXsnDiTPmL4jXuA1mMRAV7wMqu5sAsOnhBCuqjy2ilHPaiczfa1MPpxKohFHvetgUS1ysIyPEcMih4kaYVQ2Cja1G7GQ9bFBOg4HL24TOvpYu1r7WZPZ3g7JCko/UmfLFJeSwzH8NyLLKl/0i6HIQmNpFxlS0v1+FbeAOuC/3hJx8cAq1krBjqvi4drXB89aJQAG8zw/OTmZTqf0gaO8Srdv3/7oo4/wsYeYmhKjhYy8VeHBSuhahJCJVwlb2Yw4ceHLx2ChlhiOFYLgGbCyLG/evNlut1m3VZblYrH45ptvDg8PB4MBunA+n89mM3guCZ59eeedd95+++2tra14vS6P3vHJ6ixhqSzTAEFXefLB3yayNoS1rggHg8H3338PD07CsxD49BN8p2U0Gn300Ue/+tWv5MMPs9lsY2Pj4cOHz549gwfe8LmWdrsN/wFM03QymdAHrFSrXZKo0cOlYwJJbcrUrMuceUwyv/qy7Z00tcaLonjx4sWzZ8/+8Ic/jMdjfOYPHo0AyrJsMpl8/vnn3W6XFddms7m3t/fuu+8OBoOnT5+ORiN4uhNiC77Z0mq1Pv30U0uAK/EoY7WSRxPt3gAjFnlqIGYyl2VxXVU+FqqMFavfdPL+/v7jx49v3LgxGo0APAEz8YGmsizv3bvndEmtVms0GsGX1EIIwEF9WckbpfWy08FCFsFyPj3Lvz2psqARtJJr1YBwKn+z2dzZ2RkOh9999x00O9PpFLINnkDLsuwXv/gFExL3Atrc3ByNRmmafvrppxsbG91u98GDB48fP55MJuwpQ0dy2V5Ean0ZYi2q7F6dDp9+zEojL+Ua1vvUqkrNZ2Wn5AAlsNvtdjqdTqezvb0dQhiPx0dHR+pTjNQQSZKkabq9vf3o0aPJZHLv3r333nsP0LvX6x0dHcFzl6q0fld4hcjs7CtPOZPZMRzwTKWjqq3VLWsLgJRPDibVi7yDg4MXL148f/58Y2Pjt7/9baPRePr06ffff//8+XPwitrZUW7T6fThw4ez2azZbKZp2u/3Nzc3h8PhbDZTdbEGA8Gb9fwK0sr2UyYMtYPvTikwQ2Z+Jc5sVAtWtcnKNg5aHNBNkyTp9XohhNPT0zzPd3d3y7Lc2tra3d2dTqesmoZQgSzKvN/vQ7sLz3l3u91er4eZKv1kZczaTaxjN9lt1EYM005tYPEUd2oMOkUSK8BWw6VWDriSWSzmr/Eky7rdbr/fn0wmTFQEXhrjkKD48DC2vvi0+48AsKyQRcaEnEmzsDZf4aDyMDcr0dT5azQLMS1JMKAGCmHn/Hm+RqMBHzvVJ/xkTwhlFb4tgzIkSQLfpelWn/oMawXrShRvN2caS1DZ7rJCdqF5cKN1je53VcJUA3/Qb7bA4+qtVgufNrXKDx3Ee4HgZrjMZZuuja5XS35sMciVfQ/GLny8+HaDbFtk1q+qOcNVn2AOzTD6BRj0q7qKKlaWZVEU6NHXwXteXP3dmdhXSytVWSoMdRh4SqIgbegqGMW2ofaK7JukrFY7ZxF+yx8qK/LBbyRS5oGkJhDcgYLXZ+BXbyHp4TJX7hjT6l+GIsHA6jmC6E5U8XimhipqOTKtra0aN5IoJMBXFukSuP+AM1V5wP34TXWcDH6Vb1cIwuhWf7c2xXCQrSI9i6ajjU6w0UV541moJkEi2uiVlFkJt7H4p2lKb+9hTFCv0CV0I0joTqeDEUCLNKtMUuurTdNIQuF9YFORTw5Wbj4k1dZXunMl+F3POuC5TqfTbDb7/T4MYuNDMTnYyIndL8qMfqWTY7Dn8j5O3AaYZY5DLHYZgtKRi4e5GXcV4ldy6nrABTUVvq7KLksS+8veTAtIViat+kqAyJJ/mdKjMlchND7CGP4x8V5bjfW3amqyJrmWYqJPEvuWI7Ki70aggqnlhxYhxrxWVHbAQOuqiOItKlKb0KzYS8fBAf/aBe6n+tJJFEf0lcj5NxnzNOsaQtVGNDpjiqVaYuUW65GzNVXHEdUawb803Cs3H1ATmpGU3dVGqyRoUyNbcTkHFIF3nLx69QqecJB+lajFSgw9vhKPOo0PG7FmluJq1ZnP36GvrqTbr11aagnTAl5PAq+xoJEYs3t5/nxTt9vF92+h5JjKVFMKs/QAV7GDNfS6/BJrd1VUXmNkW0SVX0mxNWpwODc9PO2AJZC+d1AtFlTg8vyqht6QkjGxagP4hlCqtlNTs9mXVr9OpZ1wUm2X4/0aYymVYHf6TxV2iSkn4zGICrefWFBKDmp2MkBmcXBVQEXbPT9cWGJQgRkI4Rz+T3LZ/QYtzGOELquv1Y1ZEqr3GdAN7NlBqQbjI32JfNTJDIGDcC0dvBKSsBmZrE6CcfhlgcnC39/SkWOlbhm3Zm0aoih7rbXPB+bTCyS6nGVtol2dWxh2VTgcX57kHJRWIkoJD3NbZdnpmyLlWMkEdCY4AP/LvWpwwEPCUJXRtcG4fmC1Bpmosl1hsjL+Dvb4LpDNwcUlDQ1V1c2WPupOFPRWIrQsNMBQHVNC1kLcC/4fDv+rOT09hZv7eONQ1uAYjS4T2Y7AtEbUcqaByCoLszbPVMcNtXEk569kAiouvDey1Wq9evVqa2sLXCITS+WQ53m3271+/frh4eF0Ou10OpCytMDLKiXLqqq79K5jMYuVBQM+SW4q3gT2hL7sC9iI/EiBOlKUWrnH4zG8snNzc/NPf/pTCAH+65KmKX39l8qhLMuzszN43Pfbb7/9+uuvl8slhMjW1tbm5qbsGyU3qhTrHOmc9ZphPwj87k/9y0QN7N2EUlBfaKsCMWkcDiodHx/P5/OdnZ2iKL766ivk0+v1NjY2bt68qa6CB1ZCCP1+v9Vq0df/LhaLJEnG4/FsNvvoo4/wSSXpM0SXGG9dVX3FGIrp/uRfdNkF/Fpo4OAbHoMcfo7Gaw4PrMDTZfv7+1BTQwjwzlSoGcvl8s6dO/KhlhAC3D+6e/cu3KkA9N7Y2Nja2up2u/CAUpqm+Fo9hsCqmlbFjUzTyM42hk9kTwfj/GsX7KPkyCSuzdGVMhXeVv/zn/98Pp8nSYKvPYfeB1C01+vJf5WDJN1u986dO++++y6LJ9ZWMPmdopOIS52wSrBGzkHLx3QtzEHUu3h88XQIPcA9qBUwNWsFjVeJzYf/jV+/fj2snughBOhyGU9fYFmlcFOKQ1eFtHTf4KKFI2Qtc+Ulzhaorlod16im4RxFr4oiAZD9dSzILMNyOt73NENY2jhL5LGMyID/JLcWMGXYTKatxLErD/ArJFVNVmh8cJYuCSuGMqsFtdcaUmD5Mcg3nlGJWSHxxWUuLM+JTVsjfdfLeEZwkQo/FhI03+AxQ2A6R2KYTNBIhGSbqnNkTxDcOMDjiyt61jioi1cqqGo1isld+KGfsizxNxLn83kIYTKZFEWuLhmPR998/fWTJ0+ePn3y9OnT4+Pj4+NX//av//PZs2chhKIo/sd//29FUXz33d++/POfVUWkvrLDRKVYwaJ8fBCmdo50PxNJ7sKSp8TfJGexpoYGU6lWjvgIoDMfP3785Zd/fvTw4YsXL77+6qsQwuHhoy+//HNZlv/r3/718eFjlcMPP/zw6tWrTqfz4MGDF8+ftVqtdrtz+5/+aTwahRCePHkym82Kojg+Ph6NR7PZtFYktXbIKqguXJWtQ1b0MKfQypjQV8Oy2JG1xNrSkp7NiZw8GAwm40l/Y+PRw4cwuNHfCGU5m82SNHnx4rnKYWtr+9bt2ycnx420URTLXq9XFMXh4eHBrVvj8ejlyx9ms9nTJ0/m83m73T46+qFWF6t8BAP3Vq0R1ObOtJVwDmW4+GGEUPWr9ErtlpK7P1kiVZIke3t789ms3W4PBicfffxJCGFzaysk4eHDB9ev33j5ww/z+azVajNW89nsdHDywQcfbmxszuezJEkmk/HGxgb8IuI///PP79y50+32bt682Wq34avHDiXiUtVpYdS+oTYdY8xbVu8QJNVGV2YdTuYXMKW4bqNn4RdmnH+VUIEmk8l8Pu/1euoNIIuOj49PT0/ffvvt4+Pjra0t+G2W0WjUaDTg93fgxpC6Iz60dlUEisMvxDUaDXz7Et002MENv596enpq8Wd9jMUE3pGgLle3Vn6XxgHemLDCVQzBVAnk4O7u7u7ubgjh2rVryGpjYwOOZXyUpF+9Wo+yXdiO1J3StTKZ6LHMP3VHlmlBM7IqLX+akM5W3SmBSJ1MBx04Sla8Zlc5rL2WiSHHVe1okxI0i2PRtRzgo7ecT/k7pQ13rLzvV+7NFLACU4pI01TahfJ3/Mqy4Ur8R4nZqDbXZbaFqk2kxdTljm9YNaRrVTOySALi3S+Vj4rCsFSNTVVb1SKOXdgEx0aXJ6mjM4eKRLsnNcTVvKRrnZmyh2JQTB3B+MOB8sMILCIkDshTajJJczBpGL0Jt71RYrVcmsVvo+gch38QLmCxLjO45mFuxitSJgso5OD/L2R1EsG2WNCyML6VYz2mKgnlSSeb1wAlIcnRT3+pXqheBMtN/SRWxasduUKSyceaBmmrSIYxE9juuFdiXLkqP8zJ2pMYUaxigB8pXKgobdX/GJ0vT7UtkjrNgi4riVlAW5taHNQdKSjiX/2rjLRGOpBCP1IR/SWqWHLQt7LEjMu4uXYv9Ae1jAQzPKsCCQtla1PH+LWAHCj8UiSR3FV2VE+af4yPFVyqQBbJaQ7/KySaAVJxVRKaEoxbaV+5+jNjhKQf+de2ndpAtWKODCICaHRHCuQU2jXi4EqIBa4lpBr9QbusdzKMDlpFWoYyy0kg/l2aICzIAjAYGSz3pn/lxkELdgZlDH/grF/X3wSptUCtOyiJZS6HmO8l9komJemS6OTK/1MtozMu1k40yqxmgS5UkdkxwRq1+c2RX94s+Vmgs7PqMZtDITAhzRcd528rUYUI1Tiik1UfJ9VOj0GKKm68VnLwTQMyQ5T42JJJQnNrPWFYZFAcxeOUelhuaVU1KyQZ5jhW8COd7cIWWpu+aXIqTqheiMtV1kdnL2Yi6ik6jTVDJf7ScSlaHjZokZpejhqyFFnbqeOXifG1KXJHKwTXQxfZZEkMoNWKnvX+9aZKwMoyK++OfJIhE0iV7w151GElT8XDO81UiXBsgiObJVUpeiKaqTie4WZhleTwk88RzlFDJnptY2VxqCU1gGp38VkFW2XaVazEsFYqybPEnwVjDpcVkeFzKfpby7V0jhykAKLCb4yJ187gyFW10xyIop1KfDWNkYQhHJ3DL2ms5k0Ci5QSx5m/HSlZUtZWIOwFLFWvhCy9/MkqrljdUy0xmJWCyYTG48oLJxPjRgZzZ6imkcVdFUsS5RnvHqcXuzxZ7llVJNWY8UycMPKxkL8+Kgjp1RJN4Vpu4J+iIeyUCqYA831MJZMKX8b3kWX78rCh1lQ8xRxBD/BY+S9NMGITy4PjM5zJtpEBIaPYQm+mj9zLsqMExrUtzsJopYWSiUoWHPpEgx4XZjLYaUiW1f6IFXxaLWTlUE/JckgFYH5VYRBjgm7ELC6XO76PIRlea8z0AwJllrayghs9xQ6Uf5JTIax8T1Zv52Skq1szz8n8lmlnxaW1xaq0Rv1eyT5ybXANpWIvPbi492uBMCNH0PhWgsWaLNhsssxdpjlyoES5xafXlZAEpPiFVN/1ZNNfrivzXZ3AbOdoQiHC50nZyqLO/jK2VJLazGai1tpuDeOuVImZQRjYsBAPRM0g0iCVABu0fLey1qlVKurSiljbNVA9ZfBK/n5Y+JgsBy+fvishsJ/NsrOhgMSUVZ77Vf2vJrQvkxWk1E9BS2iW00wwFYFV3RwwcEakRldSmH3yfb8qtPBHRGXmSeOyxLX8HROnieh+nfZHReOVSgDOoVn7JoAXmccEhAQeZl6moAWQHH6pHOox5a56S83UWpVUe9GFsuUJ1Vyk8shwlD62KmtM/PkTJK2af8HOEJSBoq4VwSnzOStjdFzN4EAyQMoXGeOUFY3EJK5HU2OWhbMMXGRutQvOxyshJn9kTluC0fHKvV8ndgKxpsOOCe3kPR2vZcU4sGgrq/8GUcNLHfcjQ916VYqJhtrstDQKJDRxvCzLi3fTqwc41QFeJgHdmFk/ZlUgqS+j0gJPiygyy8bKYeVUrCun2kxFyVXXhGrSJ+oT+mwEYXDVGhnfJsgSwPSRlFSvQdkpJoN0Z0kokFyRSeP78go9LaONkdUiyCX8kkZGzdpyq2ltCe1jI5sWk0YslmuxV82DQIJAFbs2ah1RZdWzkBZnqrVPFiP9J0yYMn7RXU8ldaPayc6ExLg6SrR7yBTQmKhychBRvnaJDURf6sLEuFcs9ypJC8nsgDyVnzCh1rmM9KEuwK0c8tfiKVUxNdgD0Uu6k1pWncxGHLCpDWI1YhxlaUix8LIKUJIklVfDqnVYWiGSfP3jmaiTcZzFexApG0geMIsEtwdkIFxrgRj7MGBQUzOSlUOVRqk8J7mTWqV91mvAtcpE3Y6ZmDlGTrZ4UitTYhXHB5U11KHEFHHSV7pASljSb5KriaWWJUe+qyVWydhZWUGpP2QtDKLTjlEB+dBKvJ4K9KMsHBapikuv0ehMVVtI/1sw+CYo0nwSHkM1YK2FahGVGcnc78CAs51fLH1SY7p06zqMV2oqlYCVVZ+XtcF6tSEyeuKDnYrECi07lpAujyVPlCFS3/iZbBca6w6HyhP6FjsaJjHTcLKcf4UZL1skekqV1lJELZmq6WsN6lO5SuO5dpa/bpT8ihXDqLYF8AVdg1imUi0YnKqCyUSk5TNokcHqgmxbpGBMQjozppTSykjH/UKbIq7K2dJqjhD/b8mPuVogZTjsBwRbEimM2rjVEgiTVC/A8C8luuTi5gNtdIMLburelrHWCAV4371jBRXqVVbUSbUzQxUe5WS1sSjF/QTLGmwLvxKpBYUudHqljGlCEUYu85sudVxl4iizWCzu379/dHQEaxuNRqfT+eyzz9RWsJacosCqI0MshyG1j/RocHOAxkqpXVNQqWpdznyEHzMWm+yAKWBVC0sNdQlVSS5J0/TGjRvffvvtF198MRqNZrPZ559//tlnn6Eyl68CTomRkyV0l6LZUTOY7ajykTvWFmYVUZJqM8vhN2jZ7Qego0xyTo70Uu40TW/duvX+++/fv39/NBotl0tfVZUWi8Xf//73s7MzlCHLsg8++AB+6M0hP0vUiPTrNM3ImBYpaK5iG0mP0FMV+A3VoGMIs0aKxHQcKjUajZs3b06n06Io2u22AwYWJUmyt7f3zTff/O1vf4MXv9+4ceO9997DX+9zpKLNhD9NYq8EYfoxUhEaBP58VkSAlNetByOp1Zm1wsWIrlKn09nd3T07O1vpFfyUsiy7ffv2ixcvBoPB0dERlSfGo0GgHIodqvEqTS+rHVuo6i6zKNGuZ6zowfkZ44vz6N6OHD6pBlLPSjGgRer1es1mc9VgQmq1Wm+99Rb8PrkDvJZqMSozaK3l73Qn1PjqNMmH/YVx/vupdE1YN0GZlPLYJ1Qmy7JOp5NllW5uJWGSJOn3+/v7+/DryXRcRq08W9sYxnhU5VkbLvE9qRxJmVYUSS7TZ5bntN5yECNNU/aDJSt5lEZGv9/Psvp7onShM37JWPe3UGFZfsS/uASn6TUVKCYogq1hbZG3iCYl/MT8GkwCQbN2u93pdOAH44IoCnJfxqcUvS4zYkyx9EVlrFTBpF6qeIF9lVFqS2OTTmBbSk0QAFZKVgnXa2cD3R1+GLs2OBxp0WFYvTBknRoZTwwgrbBQI4ZVz1L9J3lpdF9qgVTP4lqWrPEBG6p29FdZhGsBxi//c7tgMjSOVfZ8kRzmwdaaVnF1FV3Iv0tDHalijpQyvjys2uOEiDhwltO1aZo6meoLptqRfn4YBVcAAA+qSURBVJThG6ogZ621dqkt6mwV2/31c7/S+pEF1ZJG1bNWaMkkTdPLZGqMMJY8TtGS1dSJ4Ej5ZazIjxIgWX2E45SCdVm9RYIyqccxUlqTI519yVrFNI8PDtb4SMHoNPZR7hKpwkqFhqEposXrHoK6k3nUMnEthtQa0Xd2DOzHkApNMSTrkcpBTQMVdSN3rEVdVkFlQwMH5iVNojXutThGWaug5IiuEvxvdT1iJrgMHycKL8k8hqymSa3uwfoFKZYx1MG1kYgzrfahNixYGDkzfaKZehk+Dl2mE/RZqcRiiH4sSYebynxiH9E0pbgGl2KV5A7kJXPUSoJVmQCfoiguk/T+FrWDtUGMYefXLInStKbiXwV+1dzCY3UzdS2tr2qVpaLQcbj8gEHpDLbEqd9pmjYajcViAb8y3+l05JxVSYYaywc5KD+qPCOLGqOSXDfjYEb7KCkfRjoeUHQNVZezzZi5a2WiKd5qtWazWaPR6Ha78K+30r6AtkxQFMXBwcGtW7dgZLlcXsn9hzVW+bgVXx189dGAlf/SUJ+z2RQiLPuqezs5SrnRCWVZHhwcHBwcwMI8z6nEwYgkNthoNHZ2dqg8ZVlG/pr6lRfg2lDwOxWaYDgiExRPZWruMxNTB6hgS4NOisJ8zFzItqbOsJT0yxhNejwV6VGLOeXjzHcqkaWICpOhGltSJP9U5Y4S7XQkftYWBpaaOEK9WJsEsp+UeRzc0PZd7uxVS7JPCeTKLabWOmwdgVmLRPeiBxeARKcyx7DZVjaz/VhaO7pJQSmwq8ozUCmrHZNju/iMr6WrAmcrxSkx4FVhT7JNg5YNQaQX8Foul0484gi1fuI+Ts28qKIC9T0TyYFxaRf1LJVZVad2rU9OHFvbsa1lblhQhOOVH/DzVWKJFcQdBuoblnDSc9TWjAmdwHhSSZhuGB9yoWs3zlySY5xar8iNVJGkLkHT3coBJon+Ig9md5klcgM8JU0gNVFxA0NE8rRMwCLD4i+3UwVQ91KF9JlELmfjqhgSjVTzssFMrknIBQxzJ8UBZkqZx0xcxwSlaImDEbxsnEXAeq6K900k4FtrGZ/aBkI9kNWUcoORSqMUNLerVVMeUwilZEGEWilp0MjJDOQt3SQrVQu5u08Y5atCLl3OcsASCedLJvTASpjXtwlZJZM5iiutfHKAJR6y1IKhgudsNjs5ORkOh51O59q1a51OxypX1kZSTr+srhQBdKG6kVWnraqB89UEpUYu4d6vCuhqTKmGs8qqvypogeYrXJ534E+ePDk8PPzHP/7x4sWL+Xx+7dq1n/70px9//HGj0WBApIK2ytz3lpo3qxJLmPgl6ipqVWq9BL5LY1UpXOBDIk1xKbe0Jq3WbA4r6qEaoTD+6NGjp0+ffvHFF99+++3Z2dl8Pl8ul3/84x9/85vf/PrXv4bH+akYljPijWsF2Xrk+AknUMvLIhpEGjAOlTtnFGFwDUNdhtK4FlexvVmRs/JVVVXqPBgMBoPBgwcPBoPB9vb2/v7+3t5ep9M5PDz83e9+95e//IUJJg3HgsnaqxafIkmN3VXXWh61ePKfr2YtieNOhpNqh8JgkC1REUK1Mm49HA7hmeybN2/eunVrf39/Z2en3W632+1Xr179/ve/H41GUm3kTJMApzGVpU3VjzGkLmHYJpdgjZTqM9Usafk79MO58sy4NGXxrAOzqlbSo0w9ifx0eZ7nSZIsFovNzc2tra2dnZ1+v99ut1utFvxP7euvv75//76KllJ4y+J44Ncdn9RAcQK6lpUKcsz+uJ3yDn2anSy0awFE9QoTiCGJurVMKaB2u51lWb/f7/V67Xa70WjAo/dJkmRZNplMnj59SoVnIS/BmW2hakFNFmMEpjhGEtXOihJ/FzU+mLKJbJQo97Las6jl0EISB0vZKaqz3J0ZCB60b7VasBy/IYNfuXn+/DkzigQVOmIZUeaTVKTWu6wE1Ka4NUG6QHqEHvPvp0psZCtxUAaLdHBMUFNbJ0adZjJAgi6Xy1arBbkLj7xsbW1JQ1jClNXi5IdvsN3MzqrTKP9a18oIUAFPCoBn+W+9UZyxRHcmq0VUjTImsZxvsYWz4NdmswnfXgVqNBobGxt0GhUYA8WCWWnxWgdQqWrxLJ6hxUTFPwnCqb+GHTvFgOlGhUO2MX4Nwtb0mP5tNBqAujC/KIrFYmE9MsiKnBRPkpoZvvqWdithr5zMPlI8YMAGW1e+h1ubUmpqqsWfScO8q3Jw9FTFKIpiPp/DzYfZbDafzxeLxWKxoFingiqVGTVyYJN9ZH6NhESfYjA5CNcwVMc5yttZJC/6USKVI5CEVkslJpwVH2ma5nn+8uXL0Wg0nU5Ho9F8Pj84ONjd3Z1Op+PxGB89ZJtKAGe+VDWSc3Bcpqz0nwpIVqTSTVUvyF3UVAzsaxfUoCqGUDVU01NSq5czuZZtkiTz+fydd965c+dOWZbFOc3n8zzPIVPhzr5ae6SaMk3V3GL4qU6rzVcLyXyEl2IwnuxUBX5VcaWPIzMPubHQ9msY01NKTB80hNoJj9dIkoZQc0vqHqoRIKE7VD3BJjNW6ke2KeVpTZNiULxkwJPgDyPgZxWj8ax8RskhamIV2RwdLJ6NRgNxOFKGULWF5VonjXwtcL7UKN5WvgDxhRlmXrzv1+8IGF5Rlawtk3NS5fYVlqv8guQLw1DL2sgSlWnBRhgyoxhylUUxeUwnqzBAVbt4mlDysjCHDbJssLZfiRjmO2kd7FiR8lt8LOtLSFdjmi1nCR1Tp1QZmAFVGdSFaWlEgRoR6saOrJGhalFptBhWOjrA4EsrU43uRclZS8UwVbJ3V8u8BCGWvqrXU1Y2ZOZFhptqwbUzFQVQ3Wllm7qjCmJUSPlRZp6arDFrawlXOQvRBWodkQl9cfNBCs1Cw6mdlpUvk6lUHgZo8fP9JRgiVEcrYxKjkbawN1J3KxalnI6P2OSLpwlZyYlPEUsICeAxayWrmE0l6tYWHiohTQUrTVWPsk1j0k6uip/gs72AX1a38JhltxNN0ogwGf7NiTdj14Bi2ZWoOkjm0vTSW/58dtaSxEJmdUdZyCJDVgZfqPqFuqCUX2UsSUNbrnKdJM3a6XSKooAH/uCfKnD/PUkS+I43JRh3Oh0VFeOFUYOSIgrV2lprgbOzqZzp13hVBbUgJkbrmqj/JHdS3kIe1b7wRsBr167h9mi15XIJ39pfLBbwnWJwM0vu5PyFk9k5QQRI94eqEVn98zHGUU2dQ63JsoRNUxkyo1n7wi7L5RJu+Mjy72xRuU3IYEQVdKWMabfbIBO7nwdvdeh0OlZAUH2Wy2We54vFIoQAN3thBGai18HfeABbwLNLEj/XqAVsrYT3EOdOOpn1MVR3+E/ifD5vt9vUo6rkzOWV24RBOJIlfrvdHg6HkfqH81SrnYYup77HhayiMCFRTwiCPM8nkwn8Dw5+UQERPpCbi1gCKAbAWRXQWHkKWoYxXF21VwI1wZfwn0T4CYF79+7RfSWsSnku/vWmllX2EaywWCyazWaMxJEU89LWUPUuUjgPbUxN+lp1plc4f90L/J3P57PZDBkiNqBXQN+iKPr9fm3ttGqTPGbQDb6EfzRNp9PZbDaZTMbj8Xg8/uSTT+CBLLm7jDMc4TUVXchyFGl3d/fZs2enp6fdbpcWwrVftRxD8f1a0JpDCgD4fu4gLC4bGVgbzl8uqwrmIzkFRlaMw/nTGhBb0+kU/iU8HA5PTk4mk8kvf/nLDz/8kHVqqh0onocQzKsumbh05OTk5Pj4eDablWW5WCwmk8lkMpnNZs1ms9vtts+p2WzCQ7n41vRIx1w5lVrxLqsUyFvzaECE83e2s+XMo6zyJef9/8nJCVsCfSLALPXl6enpycnJ9vb23bt3P/zww1u3blnmUtW52AVEpw0qax3loOSLE7AeTCaT4XAIsg6HQ3gpUq/Xg+d14WFd6GPR8TTpZXPrq3G1JL2lhj6zjJzf7XaLohgMBuE8StS8HAwGeZ5vb2//5Cc/ef/99/v9PvIJWlJJYgJ479Bny9QGgX2EJ/yoQFRPrFtQz/AhI8h1yImyLPM8h6ud5XLZ6XQ6nU6r1YIHB7GjYe8BvkxDqyrrfGTj8iwbgRIOvpxMJtPpFCL+9PR0MBi89dZbP/vZz+7evbu1tSU7gCCuoCSGB+n+SFswvsFNHQvr2PaSM5u8XC6n0ykgFVzVgHXgF6HyPG+1WuhpbHTB/dT3K5Vkn1aKnm63e3Z2dnR0BI0P+HIwGCwWi36//8knn9y9exeeaV0VZleAX5SbrpRQbE2jvlFRWvIMItxUiVWoD9XLVnT5dDqF7Eee0FsWRQEXx4D/EA2014vxerxTgdujR4+Ojo7Al4PB4ODg4M6dO/fu3dve3o5/eKM2i/jWKnqoeWZV3KC5n00Imu/VU9aE4Hqd8WGD4fxSGK5ioeqXZVkURVmWs9lsPB7DRaG8b9VqteD9iBABVurLffM8Pzw8/Otf/zoajbrd7scff/z+++9vbm7CcrVrUc0S7wIUKcHfPLQCsNb0Uis1rdUCIH1mJaWz0BHYWch0KUlTCokO7cxkMnn58iXUePyWBzoGfrgMUx8fLh8Oh0+ePHn58uXt27fv3bu3u7sLeWkBlY+xDpipTlH+J2OVQ2vvILLQEo5yq0USlaeFzJYK8ZDlLMQtkCDvi6KYTqdnZ2eQ+jC5KIoQQrvdvn37dr/flz/sUGsl6XhHTnXwdaY66bwS1lnob+ViqPOxPKsudzioS9QscSIgso7EiOTYMwgTOftaWyfwT/Kg3U7DDeh4Wb1dZ02jTBwMZONIdDu2O0saVXLGJFSBTpVKGresEoYviiSTidmEbSqlkpKgaixb2EKqO51zIRv9VfegxQhDAyvqVZLBxZaoEyw+/kfGFi1lyekjhNwrJkWsJGZ+8oV3FJci0fGLvWgAMv/JjYPWsFmSqVajillpFA+k1hyfic8wZkQO+i4PRnA4rFbdlH707v1a3GNyV8ZsMPCAclYHg4Z1tcSixJI2kgnDw1p3yni1gtj6qEaA5Wm+lpUKHwci0dKyjupgf1VkXgbb0Kvyl5qup+9KoXNVwgNdNEoqdzyWpdv6yA7wFHYBchqSylzyDMKXPmLLVaqOlFgxslZJIwDhQqqaehCIbX1Rg/CIFAAO+Ju5mX1V0SlTZgXVLkCoAFIgYUS54VmVuYwnin6Wkyy7WICvMg/EDWyEKUI/qglDg0ZOVqWlg350KjfVVPBh6tUivnXM7OLbMZ7WWCWXSE3fBNE8tsC/drm/xUWmUlIxTU0RFRhVe7EDxhaZr2dQq31wQEwu8ZE8BgOcTekuCGA+wxiSkB5C+L9DMElS1jwyOAAAAABJRU5ErkJggg==" }, "Event": "nodeQueriesComplete", "TimeStamp": 1579566933, "NodeManufacturerName": "HANK Electronics Ltd", "NodeProductName": "HKZW-SO01 Smart Plug", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Binary Switch", "NodeGeneric": 16, "NodeSpecificString": "Binary Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x0208", "NodeProductType": "0x0101", "NodeProductID": "0x0005", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "On/Off Power Switch", "NodeDeviceType": 1792, "NodeRole": 5, "NodeRoleString": "Always On Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 33, 36, 37, 39 ]} +OpenZWave/1/node/32/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/37/,{ "Instance": 1, "CommandClassId": 37, "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/37/value/541671440/,{ "Label": "Switch", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", "Index": 0, "Node": 32, "Genre": "User", "Help": "Turn On/Off Device", "ValueIDKey": 541671440, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/39/value/550092820/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 32, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 550092820, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/,{ "Instance": 1, "CommandClassId": 43, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/value/541769747/,{ "Label": "Scene", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 0, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 541769747, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/43/value/281475518480403/,{ "Label": "Duration", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 1, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 281475518480403, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/,{ "Instance": 1, "CommandClassId": 50, "CommandClass": "COMMAND_CLASS_METER", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/541884434/,{ "Label": "Electric - kWh", "Value": 0.06199999898672104, "Units": "kWh", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 0, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 541884434, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579566931} +OpenZWave/1/node/32/instance/1/commandclass/50/value/562950495305746/,{ "Label": "Electric - W", "Value": 0.0, "Units": "W", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 2, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 562950495305746, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/1125900448727058/,{ "Label": "Electric - V", "Value": 123.90499877929688, "Units": "V", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 4, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 1125900448727058, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579566933} +OpenZWave/1/node/32/instance/1/commandclass/50/value/1407375425437714/,{ "Label": "Electric - A", "Value": 0.0, "Units": "A", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 5, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 1407375425437714, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/72057594579812368/,{ "Label": "Exporting", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 256, "Node": 32, "Genre": "User", "Help": "", "ValueIDKey": 72057594579812368, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/50/value/72339069564911640/,{ "Label": "Reset", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_METER", "Index": 257, "Node": 32, "Genre": "System", "Help": "", "ValueIDKey": 72339069564911640, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/550993937/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 32, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 550993937, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/281475527704598/,{ "Label": "InstallerIcon", "Value": 1792, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 32, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475527704598, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/94/value/562950504415254/,{ "Label": "UserIcon", "Value": 1792, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 32, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950504415254, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/5629500081307668/,{ "Label": "Overload Protection", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 20, "Node": 32, "Genre": "Config", "Help": "Smart Plug keep detecting the load power, once the current exceeds 16.5a for more than 5s, smart plug's relay will turn off", "ValueIDKey": 5629500081307668, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/5910975058018324/,{ "Label": "Device status after power failure", "Value": { "List": [ { "Value": 0, "Label": "Memorize" }, { "Value": 1, "Label": "On" }, { "Value": 2, "Label": "Off" } ], "Selected": "Memorize" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 21, "Node": 32, "Genre": "Config", "Help": "Define how the plug reacts after the power supply is back on. 0 - Smart Plug memorizes its state after a power failure. 1 - Smart Plug does not memorize its state after a power failure. Connected device will be on after the power supply is reconnected. 2 - Smart Plug does not memorize its state after a power failure. Connected device will be off after the power supply is reconnected.", "ValueIDKey": 5910975058018324, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/6755399988150292/,{ "Label": "Notification when load status change", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Basic" }, { "Value": 2, "Label": "Basic without Z-WAVE Command" } ], "Selected": "Basic" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 24, "Node": 32, "Genre": "Config", "Help": "Smart Plug can send notifications to association device(Group Lifeline) when state of smart plug's load change 0 - The function is disabled 1 - Send Basic report. 2 - Send Basic report only when Load condition is not changed by Z-WAVE Command", "ValueIDKey": 6755399988150292, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/7599824918282260/,{ "Label": "Indicator Modes", "Value": { "List": [ { "Value": 0, "Label": "Enabled" }, { "Value": 1, "Label": "Disabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 27, "Node": 32, "Genre": "Config", "Help": "After smart plug being included into a Z-Wave network, the LED in the device will indicator the state of load. 0 - The LED will follow the status(on/off) of its load 1 - When the state of Switch's load changed, THe LED will follow the status(on/off) of its load, but the red LED will turn off after 5 seconds if there is no any switch action.", "ValueIDKey": 7599824918282260, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/42502722030403606/,{ "Label": "Threshold of power report", "Value": 50, "Units": "W", "Min": 0, "Max": 65535, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 151, "Node": 32, "Genre": "Config", "Help": "Power threshold to be interpereted, when the change value of load power exceeds the setting threshold, the smart plug will send meter report to association device(Group Lifeline)", "ValueIDKey": 42502722030403606, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/42784197007114257/,{ "Label": "Percentage threshold of power report", "Value": 10, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 152, "Node": 32, "Genre": "Config", "Help": "Power percentage threshold to be interpreted, when change value of the load power exceeds the setting threshold, the smart plug will send meter report to association device(Group Lifeline).", "ValueIDKey": 42784197007114257, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48132221564616723/,{ "Label": "Power report frequency", "Value": 30, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 171, "Node": 32, "Genre": "Config", "Help": "The interval of sending power report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48132221564616723, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48413696541327379/,{ "Label": "Energy report frequency", "Value": 300, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 172, "Node": 32, "Genre": "Config", "Help": "The interval of sending power report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48413696541327379, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48695171518038035/,{ "Label": "Voltage report frequency", "Value": 0, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 173, "Node": 32, "Genre": "Config", "Help": "The interval of sending voltage report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48695171518038035, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/112/value/48976646494748691/,{ "Label": "Electricity report frequency", "Value": 0, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 174, "Node": 32, "Genre": "Config", "Help": "The interval of sending electricity report to association device(Group Lifeline). 0 - The function is disabled.", "ValueIDKey": 48976646494748691, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/551321619/,{ "Label": "Loaded Config Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 32, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 551321619, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/281475528032275/,{ "Label": "Config File Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 32, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475528032275, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/562950504742931/,{ "Label": "Latest Available Config File Revision", "Value": 2, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 32, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950504742931, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/844425481453591/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 32, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425481453591, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/114/value/1125900458164247/,{ "Label": "Serial Number", "Value": "0107020900000000000607034800010001000000", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 32, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900458164247, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/551338004/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 32, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 551338004, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/281475528048657/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 32, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475528048657, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/562950504759320/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 32, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950504759320, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/844425481469969/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 32, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425481469969, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1125900458180628/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 32, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900458180628, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1407375434891286/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 32, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375434891286, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1688850411601944/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 32, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850411601944, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/1970325388312600/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 32, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325388312600, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/2251800365023252/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 32, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800365023252, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/115/value/2533275341733910/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 32, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275341733910, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/551649303/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 32, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 551649303, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/281475528359959/,{ "Label": "Protocol Version", "Value": "4.24", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 32, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475528359959, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/instance/1/commandclass/134/value/562950505070615/,{ "Label": "Application Version", "Value": "1.05", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 32, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950505070615, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/32/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 5, "Members": [ "1.0", "255.0" ], "TimeStamp": 1579566915} +OpenZWave/1/node/36/,{ "NodeID": 36, "NodeQueryStage": "CacheLoad", "isListening": false, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0086:007A:0102", "ZWAProductURL": "", "ProductPic": "images/aeotec/zw122.png", "Description": "Aeotec by Aeon Labs Water Sensor 6 brings intelligence to a new level, one that is suited to both safety and convenience. It contains 4 sensing points, which would be more accurately to detect the presence and absence of water or detect whether there is water leak in some places of your home. The Water Sensor 6 has an inbuilt buzzer that can play alarm sounds to let you know when the water is detected. The Water Sensor 6 is also a security Z-Wave device that supports Over The Air (OTA) for firmware updates.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2437/Aeon Labs Water Sensor 6 manual.pdf", "ProductPageURL": "", "InclusionHelp": "Turn the primary controller of Z-Wave network into inclusion mode, short press the product’s Action Button that you can find on the product.", "ExclusionHelp": "Turn the primary controller of Z-Wave network into exclusion mode, short press the product’s Action Button that you can find on the product.", "ResetHelp": "Press and hold the Action Button that you can find on the product for 20 seconds and then release. This procedure should only be used when the primary controller is inoperable.", "WakeupHelp": "Pressing the Action Button once will trigger sending the Wake up notification command. If press and hold the Z-Wave button for 3 seconds, the Water Sensor will wake up for 10 minutes.", "ProductSupportURL": "", "Frequency": "", "Name": "Water Sensor 6", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAGYAAADICAIAAACVqwOrAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nO19S7Adx3ne9/fMOefei3sBiCRAkAIfEi0zD7lix7FTeXiRqmxSSSqLbLJIFko5SaVclUVW2aRUWXjhpJIssvEuKZcUS6Yky5Ys25JNPfgUSZCUSAsQSQAkQRAAARLAvffce885M/1n0a+/e3rmzDn3AExi/wX07dPd049//v5f/c8Maa2JiJmJCA7MT5nK8mwegGlsMs02Eub2mfTT/8IlJtYnL8H2la1uoqxt9h0DfLzQhrLDgDJ/Ovpqq0rK/y/ElwEzsRVOj7TWq+rrzwmoj3sC/+/BX6BsYbB8sT8YYQEwa82aGbnrmQEGKbAWl5kB3U/JW8zP/ESICRQGSX5GgzLYjZCTV7acbQtShVJUlLbf3syu7NnOjMlgZq5mB1xVYtaUtvMC1K2N2xCSGyV0yRYFbNbDAJgYTNQck5n90C09s8/YQYioKAaD0WA4BFQ7tiNYgMqYua6m1Wzqx/YUI3/mr4UlJdk4HZhlvbgwyVFc0hyVwY1+NAOGPP1dDBmoshytrRXlkNC4GQ3oizJmnk0nupoSiBRBFUr520IEZuawDVs7Mc3NujJDt82WDamFfPsQph9mcC16zW5kMMBaa601azCP1jfK4drcHdoDZQwGV7NJPZsSERVlURRobH5mZGfm2VRz96Yl8UyEGSDLyaOgc+Lz6cAPp7VmXeuqYub1I1uqHHRjrYfEJNZ1Vc+mBChVlGVJRM1OCWyKXerLQyqXQzx/aYE9RZ2xYV1E7Jhk81+PZQlQRWnkwMHerhcjbVB6q6jZo6tCPZsCgCpUWXZQpeslapBvz4202UTQV8KW2Qm/7MwXBSICWCnSpDTr2XQyGK2BwsylvcXMKr4STQpi1rquAKiisG3CMmIisj24EsfoSHYrV09m2f5S2E0nVAqAQMo0pdDM164KiFRhhM9sOgm3yk9LDKzmmmB1XYMIpIioSRxRyoHFRJxNdi7p0DBgUxK0EaSk6np0jTnq69BA7n4qpQjQVcVao2Gc+p8quTjJMDPrCmDLog69C7JzRiBD8nTZvuNINF7hLIiUAsDgDrubmbt4k21Ta3E7e8+SEkqLy6PUtGMwoptC5nJ3p1rNg5WBISACWOsOtMzV/uNtS3FH8eLD2lr7ci1CanccWS5GgA5autnTTAS2ar+RiLEnclVgbAxGRhOW0MNg4qBDkZg3gQyJuNTWEEtMkhze5V0qCEdUpXxVVMVmh8/fYepLoJcngwLtsBCRTKkqlK6IGyUZPU2KzAhb3MgbHue2ckNOHBIifbId+jl/3HIskowSafOAR1ggbIdad7kX2gCZqYkUSRoujBibwL79v2IR0NOZ0deTQRQb+iTz8eZIFH+fZw5OGwKxMxvtDidiyf+ttspgklQbd7vyHdkHZ/2dP9rvMynukvGCYpYD4cMxFySeC28PibwXqew2jtyVq2NlVsXvgbP+KAsEAXE3Ig5u/Q2C9blSOFU0XJxPxZSNrPQka3vRkqoJq8NZb1gIZak3iSLRSA5jSXlYFbHTtWxqtqLtnp0IZcvnKSfsA50K4boynPXpKKBsjonLOq+XwttRIp+lILfFyKlhsPIv6gyuIMZXpOCanyvV/V3vPXCWoTLhw7AdsGP/LR2S4+Qun2/m9miGUCHKKWpsO1VwiquRE4lac4cge8yeoqxb0Eo6zLH/dCtJZLBU1kVTs9P8Jna/ILtnsNeH2FoYxlrg7EyWB2Fltx2wK+ntaVrtmR69C9H552A1QLILkVUggKeTg4vn32CtTXdXLr27t71tlro/Hr/3zkWLL9bvXnhrur9nerh54/r1a1dNt7PJ9J3zb7C2mHn/3bfH27eIGaDxzvb7l96xLg6t3zn/VjWdHAZnYq3R8j0G8wEGPlCjrUtqlLj9lIg9Bqnzb5w989wz169dBTCbHDzz/Sdfe+UMMzPzT3708nNPfe9gbwzwrZs3X3ru6TfO/sRsvDM/fPaFp79v6O/i+Tdfeu6ZD65cJlA1nT731HdfPfOiEcGvv/LSsz/4bjU5AHDj2tWXn3/6/BtnD0V1kSot1U230A6Wb6qme2OGVuWgLEuLhTznFZJQqhag2cH+lcuXTj/6M6QIzFffu7R1/NiRzaMA9nZ2bn1044GHHyVFrPV7b184eerB0foGM9/88Iauq3tPniKi6eTg6uVLpx99jJSCrq9dfu/I1tGtY8eZaLx9e/vWrVOnHwLArN97++Kp0w8PhsMlcMXMs8lBXVVrRzaHo/W2ZnMcwcycoqyJmbY07sgkVpu3DIwpxq9V/wPLs7WmWUZq+lkKdWVp5mZRVldrG10oWzTAgNno4iFlkcYljj2bJbE/huDalBAArllrr42xrgDHg+ua69qtnVnXDmnErFnXhv0Tm0NK2CqdU4YWW+Kc+kVRZs+pRSr/yTZGGyVo/e6FN7/227+1/dGHBOhq9o2v/s5rr75k5vaTH736+1/57elkAqKbH17797/2r3/4zA/MGc9v/o//+t//868zazBeffHZf/dvf/XalfcB6Gr6ja9++dUXnzfy9ccvv/B7X/tyXc0A3P7o+te//MVLF946hPd4/tHvIgEGrYOENJI47u/6xpGT999fDocASBX33nfv1pFN02Dz2PH77juhCsXMg7I8eeK+I6aK+eTJE/sHM2IQ8ebm5v0n7hsMSgKI1In7Tx09dtwQ4+bW0ZMnTihVABiORidOnlzfOLL0WoLC17HehXmZ7zsxI70fwqfWKIJT/LU/8GbnwJV2ubWCWKi7wfsaK73RsbsT7iRqlwrAY+bpZKKrWTf7XzZYqqllZDyFJO6adgotM4wXyHN0YWQxyS3uD1AsGxAHi9HWI3NwZ/paEl/ty0tBcSekzR0TZ2Yr2Rgu5UZqL9q5fevlF56bTaem3Z+9+vK1q++beX30wbUfnXkBzCCuZ9Nvfv2J61evgBnMr774/AvPPm3Gu3Xjg9//ypemBwdgsNavvXLm2pXLZoBrVy6//spLJhxkNp2eeeHZ3e3bh8KWk+ZtsBiVMRnS8cTD7nTRlIiU2ASTALj6/uW3z791++ZHAFWz6Zvnzl66eN6Q2KV3Lp5/4+xkcgDGB1cuf+G3/udLzz9rbvHXv/qlJ778BeYarH/0yotf/ML/evfSOwTW9ez8m2ffvniemMH60sXzb507W1cVgNs3b166eOHKe5eWZv+O8rsunx8rK3mZo7tE8fE/MwoRAVrr8c725rHjRjne29kejdaK0YiY62q2P97bPHYMALP+4Mp79554oBgMAIxv39S13jp+Dwi6nl2/euX+B06jUADt7+wMRsNyMGSgrqrJ/t7G1lEAYN65fWvz6FFSxSKIspDwsuYmsw6LNpR5uzSHsgVACgnr6WEGkfdQk93ZTomlmMeDgg0f+kqqupwF/cGgjOtqtHEkQZn0ZKg2m7x7/T4fp5SU+GHGOzs2C97fH1dVRYCJKRqPd90ex3jnNtc2Lmx6sD/Z3zMdaV3tbt/0fR7sjc1OBFBXs4O9sR9pvLN9qCP92L+VdU8ojh9xmHPK4kJSAOfA8HkI0ekVXhCY3zz7Z9/82u98eO0qgGoy+ebvPvHKD58GAMarLz7/za89MTnYB3D18rv/6nP/4k/+6FtgDa1/49c//5/+438w2vwz3//TX/3cP7/41psGR3/w9a+++PzTYA3WL/3wmW/87leqyQTAhzeuf/PrX3nr3E8Opcq24MsTXfH5z38+dzAWrqxnM4BJFUqpZTYmqeFwVJTFJx9+pCxKVRQEPHj69JGtowwMh6P19fUHTp8motFohLr6xV/+m0ZNHZV49FOPPfrYZwCsrw2HZfmLv/y3B4OBUkqBHzz90ObWUUANy2Lz6LGTpx4A0aAoiPihRx4drh9ZboPWdQ2ty8GwKAeNhVjMzHkgBykvQ2MqknuknIRCGTG00ZwNQbI9WAkWOPl4A8mzyPdhD2Is63OkTfYYptVm7w8JL2tr1qVk5FyMcHMVbkW/VdGohdNKOOCL4Xl/hC9OlPh0QZHrwt8KBttjJ1ulwXW3LtoNc3fS/GeYJBAYxCZFM08AOG2j9fVrV7777T/c390xOs0zT3774hvnjDV06eL5H/zJt+vZFKDp5OD73/nW1cvvgpm0fu2Vl848/7QZ98a1K0/+8bf2dneMS+PZ73/3wk/PGnK78OZPn3ryOzybAdjf3f3ed/7wxgfX5gY6d6xwLir6xWTYvwwm/4+YSPy0/huLNNuAQSDa39sdb9+eTKYEcF3vbt8aj3cYgNbj7e3xzi2tNcCz2Wx3Z3dvPCaACePt2zs7O4aRHxzs7+1uTycTY0/tbN/eHW8bEt7b2d7Z3dFcA5hOJ+OdnX0vQJcAv+s7sNHHLAe0GgyLoug6CXdbJTca6XpGRen4pqawl1nXtSoKY6iz1kopc57JugZgwuRMlXsShLnWpJS1/E0smCoAEEjripQC1BLszPGy2Whjazhaa2vWWzu1yiPF3ldxdGsiACIfBrv2rMoBW9cFQEXoAKSKEpaLk1JF4FKqkAIfhQo/Cq/cEyvlkcNgp/cvq2Q4YdOBll4b04skRhzwI+PILScz7goSJfann4+Rc85xYQ0CkGGU8Ge/CbU6nW8FbopDwmIuRulzkValE355cyWUBsvAG0umhELqZHByn4NKKUutCL6rsKhX1kYQdHLIYDb5H8ISDAoUGYcHDLt30Y/kCToagVMhKLB8GAspgc6ezI1cFGWZXRPuvosFbcOmVWJ9GIYPfnH+RYfF7B1JS7wyt+ASOqFz2xvCz/CyzicBvIIq89LwbGkTmbHs6qN4LrtLKbF5o1n5vNi5K2VuDQKX3gqTT58uyXSSL+6kpL5t4gaCl7V0kqGpFcuDiKnkO1c9RyX7TA+7k0kAcJ5ZjtPYpc0uFRXejyuFue9BYsUWgtlaReR+smtwJ14nwJ6mjPUtUdQ38sdoXcgIN6dsSXmHsEd9KTOTrGiMFjGE2OWShssmwct3RmZmXRVInmHKehnnRqlR648lgQNVNv2ilA4Yk8BhQPSTwYlvs6AnY+6ofW44N9JmefOiVJOwfC1/ErYUGB6S5ZgS5mv/fafjbfK0pNGQRMOmtGhhDI1//n6vbFcSSZ9VKyx59NsklCi8xZaYiZg2YouJtGMEZi1uODX+3Rnogf/5ZrnkrrJxBmve59dCYTZOIDLs4cx4N1qkbbX4HM0ZVf6EcHngoGR3ORoXpLKGoIyPS6jRRrQEHBUiTlm4KZtqVzRyWurU6MVW0QKin8PxsggiovIamnvSRhqRzZYmS6LY5inJ25bWW+2IyBKjdAsEcl4J9BQji6EsQ0BRLTVK0jYZEQlO8+aqWOQLDPpCj8Q7ope1wcKejNYfkh/l+XPCw2Q3nMvPnYlnjauDzs6W8WREAhFh+WxTtmmzFmbX5aUC4HEl+ne+MGrcAme9zV/kwkDRnwTMoK0oazr5AIQnft1vj600RbPE+GmbYk7kQ+CYfaoO7BcQxuXQkkm6PQ8HJIZsVrF74qb1gZyWbrt19u5Urh/IMD3fgOHOcRKOJppZ1K9K9UfE/iOWagwMj5bwdEk/f4ZpCZ+6csCz/2Z5JLwZycPDRmAS4HUNN1Z7sEirLnIYaN6erOldtlwzp98m1uxALJAH0YYISbyMqI19GPD0laMgSQjpHTokSN1YRq/Im0dEeV7Wirv2XWCpxKeNWs/U2zuIqpxWweInmox5hX7/eDIu10DFYq/VY8n44XR9r1JR8qIMqVXAcW3xgLloTkD0rIn0YUat2Rob3rPYyrIXhp7a/4J6GQn+LCiG7DO75nQ30IeI5rF/yG0ldhfKCfrYFgZczCMsViyORJ7977uqyy56jukdjna+ZB5ohnsimhrlcK8q4OjsKZWXZDc1YFvbSyhtGDKrlJYOenTYhTIrX5PC5G/g9SGVbWIftHt8vJm6IaXh6NHl3whE4hJu3MEVQLhvrdCLyigokexL0KQUt2Lkag0nMxvbi9YE2xR0U0Ojfmf6h+vMNFy3McZSCbI8dHH2+Sjz0tWmCU9Hk2ooqvVtDNJipLus0E3dnmdKPLwkfjgbLOZkK8BXjy56ezL8tDLvrZEpIalFaA8g+Ne819hIBCLzPhvAqdZ5MiXE1LQ6tSyv/Tdhscgf8ytwmCiTpNxEl4UsQ/f5+CWILU1jJ2WfNfQEiv5kYWHfv4jpdC/FiOOn4CnHxDIKgZDoWhK7jVqjZggtJr5WMsGVsf5+ymkpb2lybpi3V9KdYddDQVn37KgxJ5MK9RVeRgktxV0sNp/ggGnXd0b9T9AisVG2m75ZrFEjE1oDQsTlptF2aSw3zXvDmxLEroSk6DRDWpt0ldhrosWXqOa5tP+Z1RS5ZSu5lJrlvNjesfNr3yZtL2JfYIx5Ewh9NTGjkt89u0zZEtyUm0dEsXYQLsps+uZ9sghngXkbUyak2wqdGfF0goyWP/Psv4MR2t1DIfXX+Pq2nSdZWK5RRP+Zy6UUsfftjjAyCYn4tl7ZRrTT/OuBiH6jnSiaJEzbnq06CRE8GQ3JIcZoQvhoj7/z0WnAYcCvqB0nix/9SqUfjnBy1k/a3vwS+hREPiqfO4vg8FvZuS8CavKSx8OC55hiK0SaklfQmuhggQhK8GN2V5raXRdCerIbncT8Vxb800ctXvDZ8s68jMaIy5M5efy0kJnDsVNUteP3HN2K0PdqCI2CV6HrBqzg1SL9wTNSy8lg/WHOq+E1WtMqRkSerSVK2qEhL70i6EtlMbciIAldyaXUkKzceEuQiYJjJg7vPJhrOYad7l68ujKO1gP5fVHGEdsWmpGZcSOFXXyirJknEPMpQ57QRZJAxigDUruxl/dcxXzw5ls7LL8xG/ZkpCPI8vAGAvOOjTbxCsTmgxjL+XxsI+mluSM2ZlefrY9KtAEJcSXU2phtB5XVCtEgXqNHOP3jriScH/YiDvw+XUAcpk2eslcEc2i2BFJjvUNgdz1q41+YnhJOaIDGi2VtoWde7pEedoLBdyStbndIzP6BqniYQ0CPq1PtHw0MxtB+B4wHInpzcXMunvxcPKc8nUKSj4bLhNT486d+3tRekONliZlZymco2h4OCBcLLMob6i0iABLVFg2CQCQbiq6ygbTsSxqT4YAjlxf6+gogu70SLxAzp6++mKNGR8ET7oWpicKBuI0pCSLOVJqrDAMj6/GHU1/CXCUvy0a1kO9jHkL6gVNlvY+s+VxB+oWcuWPL9oL9u1+yNmrpG0lkksRy7KTM+DuFEy0ITytlVsH/ifJvFZQuAHRH/mTYWWT0cdQWbM4gczw4lCQ0HWqDW8OWWA7rB0uRwpbzOVU2CXs5BETUkxDT4pE/nn9bFiWKM2e5UjJKRc1jyR0YOOspVu9Y0JxprCGQaMgL4j4e0gPE3CvwdtEwFqGqmr/ipfMRoSBvMpML8JFY8nXkbMzYZPJ48dwgt7JDBxq3bcwElnkgRzB5+dtqs76MpK7pt7J5IYnFvc8TpB6bqn8N9csp1Jy812w1MAfzCz/2ldt+aQ01SsOPxEsWmQ0sGmS6dkpMVCFF0OrcjV39zEdZdDU7zsFpg8gmlxc07EXb3OQbEhPBIBcaBimQcm/9F305hDvzf5V2Uxss4ch2EiCtylEectzBIhfBqI5TElwwCFoTFh2ENbtBezGgBaEL9at2MfaZfUA6xSWS1lhUwJcIsyLtUlYfDuasYWH2z3PS/Kw5zXrmlemjc3+FKm/5Nce4o7B4GEtLGvsx8lflLg0/Y0daYGQNph6ZUETKfHN0dSdNc2DZFz43IHMY3AcaYiT/K3dlcL3Y3wsOvSxEkT+IXRrZyJ/ESyHzUq1ttokulrWOjyfXer9F+9HcHRSRMrogyedjMtoflTDV8HIqOBRcRVyedhDb5PLakE+MO+RbuTzy+SUhIMAiyD9d4pGTfxtL991z+oHQAiItQZY7PT307FPv7GJRLiuSKSHY6qk0CTb8YcDGP8TQ9PFkvLLdkVJJGpdTwlIaXZAzq33aVpgFiqWkVEdWAIJ0OMGGhMgrm9RlsCa9F+1Dt5RzwGpIYd8paJ033Iq3cDsk1d4p1aJDAHe9JDVXyE6ZilISwReUL3cGUksa1+amy0iQ5Q8Pega/9IIeXS32cnV7HGK/Wsn2jeqwz/WSe1Ja5GHerug8DiSZTmuawYEwodzrliz7cyLn8MzM8TK3uBZY+KNC3kFjfro5x0sOKYTmCSCrCjetpSaVBfqi5HicAR/jcjjoqQ0v+Dh+poQA97J+w2+iE0ehsEklhiGpFa5O5uMBqUUurJKVCQWwq9tFvbJZLiPd0A0jwJMSp28dcyl5RuU02oD0rBbLoSRQxuHV2jtCZTJOPCgFgnlz7L5P7xe5TZw0gtjcXo9gcZoQC+rmZ3DujqfMQN8n5Sy4DxNy+ONr41MigS2X9XvTYV4wQBPqEn2sNnqrRkShTOij7NwhmP/6SkhKaX5FQlKCbZEri3UDcV5rz259awpnBnBDuTaRKLFmxSr4vltmO6lK5AQq60fbcRu7RseJEsJrsDWIGP00dMWj2G9Yzuhn8iFNNwWDyhXgrYtoxGlp+nnkxJ/RuFJQi7jhQi2i9unHJlxG2xBp9ABEQ0cJhSRlxZ2ABC1o8rLuSBb3uFCDt5N9DUaI7A/FYlHBUxJFPrHTTGzwHrmZBJFgpsReK4anttXhSx79SopJyKjMVrdCEvQFp5A1GzlOHxGLuZzthbYw/DCKin2Nsx9JSFP/M5wEr1haym0gZXcIB3Fm+ULBLJ6vR6eHDbdsHKhjiIjjJnZKFDUXqljEzfydynayAmhbdRJxJL9s3Qe4JR+rBJnUZF28JyjNw4lDbot8IpGhwPP6zbsPxMFzbW477nobS04COIvZmkGSK5ldJz5yIC0BDlcn2qvXLjjZg4JTig1rSc/y0tj3vVpow8kS55jRvA3LDsqFeDCEySmo8Cpq0NV8M5GXIpOjSsH14TikNwlz/HQZkC7GjmYLoyxWluwrU9KhbUuIv0tSQxIOZwYVvz4GG+BQp+UyltGXpW1cutziWkKcIxq8i/YlsPw5ZmOWLWEsS3YZh3my+Cebr/rVlTH7b4NlqazZ5zJnv16fiEyu+LSI2mnUM4m7SmaLObIPyWVbLpdWf9BZnVkn1DTIhpnLDwnZE6Ym9PJkiF4PkfZpIrz+Zl8KLT8/l+5NtBAIg6mrz8O88LmZUletbNKWdky4UXhHdiPN7ziclsvIg2WH67GKOUgXaaLTtva9ctQRxKMSEDgxGRNKGZDlPRl53bdtELG7+oiBpjbbMhr586S8L2ilNnnkQhA2TeLdUYuTFQmcindqAWlK6V2CXXl46ZZFhjeanLdX+mbjJSBF+MpYWQTNnRc8GRKFcz0ZZodEJ2di08hgDqcphLztgWUPoki2lOaRbOIx3pjTSiCN5WqxMbuozGJ6zgZty+e7jFLK0WYyOifyXloTvEqE9YYeqmxT0SepbIpqG5DSqPHqVhwZaw0f+1ROqJJUnHhTXCGCWnK37aV+72IMaxffpyWSOPGfPo6u9NZg8yWpABBcRdJmDM+iu0FTH1Qkr3otszf00MsW/9ij6FnmLQt3T/3mbBy2Xg+P8jAzig1G7kNC3n12l3fnYgZTcCeKsoaGn33/qXW4QqgLga48LjOM1RCUilxMXMf1qwM3SAdaym4lI90RlHzrV6Z21e6wKE5h0O08r35PuascLiOLXKyEIzFEBYHDV0pXCI4Nd6BlcU9Gm5RrU2elUoa0xLUx83SxabaIAxsI2LUQPm26Uqz12eWLhuRxQl5uJOewhoyqyLQEAP9REtsEURyGCyEKstNw+hztAQTUzYqlIQTFdWzMxbpsbEx5XOLK7TFK2lJMDERGTphdSwGHXNfVtZs3r+2MD7RWRXFsODh17OixjS3pBYrlcgH7KbVVgRTfGVjC9w/kdp58gRaJpmHnRRpHVGs01lvbN7938d0Xr9+4VSgolIRBoYZKDVk9uLb2Cyfv//kHHyiL0hGp3N0K0KvboXMUctI6+oqpV4J8ZrI3BrQqB2XZjV9JVN2QttFV/d1z5/7g8vvbBSZ6CuKCMFRqQDRQNCA1LAYlDY6Xo3/8qU8/dt89gOJ066wAZcw8nU64qkYbm4PhyJcnOCGtdUf8AYDJ/hjcB2VLzBFMqKazL54589x4PK73K+i1go4U5dagXCuUIlUzV7qumZlVoRT08B888ujfffghsGaWtLZClM1GG1vD0VpbLE94VEIiK/I9xCay4f/yzTxOx4gefoqCU8jqCMYD4EOqAeZaf/mVV5/dH+/ofaX0icHgvrXRkXJQEimCIqUZU13vVbP9qjqoK+bqiQtvMfOvPHQ6FimHRFe8QASEJD4xTr7D1B32Y0DoEiQ1BKlxyCiN4M4JvM+oaWDgqXM/fXZ3d7c+KIhPjEYnRqM15s9uHvuZT9x3bOMIiD482H/j1o3XP7xeqeKg1hNd13zwxTfffGjryCPHPyGsqpVhzXvosj4eABnC82Cqpntj7sXLgH6czPf+wc2P/ssrr13nSaVnnxgOTowGD5ejf/L4X/3E0ePGfaEUGbF6bbz9pXOvXRrv7Fb1lFGzemR49PN/528hvBd3RRtzMuF6NtrYHI7W25p9HN/6tcBPXbq8X7BGvV4Wx0r1AJX/7Od+4RNbx2B1FXcoSnT/5rHPffavHxuuMcDMmquz450fX7m66PT6LqITFj/65UYalVNaksszYzqZvn57p+K6JHV8UA41/8NPf2ZttN42483h2j997C/V2vklqX7y0qV8/4cAit0DWVgcZU1TqVmO+fmrt2/vERN4o6ANVTw6GD1438mkZeRfJHr8nhMPrq9rZiJoql/56CbrauH5d4K0Sdrgrhz9ZngzXd3bV8QDRWuqKAifOn4PSEkjKTjEvQKk1OPH7724N1YgZnxQ6dvj3eNbx8NnrFYHvY5+FwBq0Exkk7s2lNYdm9YAAAcbSURBVLPAbTver+qBovWCRoUCcHxtnUUbEphijzjm46M1cz6rmWuF2wdT32SZhSwFS2mn3OBSLHgbo7PEAgGlUoyiJCZgFmvUntKS21PVNYE0QwOaQU1X8OGAAN3if/KwzLd+ZT5OKVseGZ6uZGtYDqgYKFJEAK7cvs2skYkmkwvQl3ZuE1HNrJmo5uNra8yr9GTISbfBohuT/JZr/AttKJT4zUmRlgvcv7FOoAEVADTotZsf6WqWibXycRKEvYP91z+6CWDCYNBJwtbG2oLznwvzA7B6oUxqvv5VsElqal3qjkaSls5zw+ATx45vMohIs6oYHw4GT5076590lffBHgNr/Y2fnt0j1EwVE6P4G/fco4oBIQpGO0yARE/IRP60jKqNCWEYSDOFdR1CvAG70ZJtGyIaDAaPHzuuudSgikmDfu+9axcuv8cm/sZ+kgiwalx95u0Lf/zBdSKaMDEUquLvP/RAImEig2xJyFlI8f1IX/qQRRzDtCQnm7L/IM8imw3iB98YwC998lQxIw1VMZhUvb72314/96ev/3g2PXA3BwDvjXe/cubMb771Fgo10VQxoMu/XJY/d/pBucj0jHhF0LwBwfmTBHF4+RXZmJ2z8t6yhhKW+G9Dy5cvvvPEe9emqGasAa0Zkxrrk/3PHj96YmO91nx5d/fc7s5kNGDgQPNEM7MqDvg3fumzn37ggWQ44SNZRoVk5tlkUleztSObg+FaFl/MPOeEqYEVF+sfI4icU8j8yPQotLfgkAb//KMPX9rZf/Kj3RmqGlqRHhSo1tdePpjq/YlZu15bqzUONFdmw8+KX/vZT37q1ClHtBzvJl7IOdC+0FaHRf5RidYwFnGI0bQ1jZLU5+4GI4jwjz77mfpHb/7RjZ1ZoWrUREwgBWZiZq4ZFbi2xzRqWOPffObBX/nMY24eZp5yi5g5LLtFaY4hQR24tGtjnu6NAU12Y/olS5xy+6aUJSld2kKCruuXL17+3xeuXWdmxWwVrzAJzcQ1fnZY/su/8qlHTp5oUzU94pZDGTPPpvv1rF470uX86Y2yoiwHZaSbhyWl57wRYmP8NC8n98mTvYODZ99+/6krN9+Z6JkyDTWYN5gfPzL6e6fv+2unTxXtPrvD4wvAbLpfV/Vap79sEZSVZTgRsvKR4HFiJIY/zXSpa9+SD0Y4zCOdqPXOePfa7v7OtCLCPaPBia2NtbX1XOxBYzHzNk33SgFMJ/u6mkNlfW3M3Hyp7Vf8NFKoTvezX5499zRqjzp69OjRraMwGtldcLTIWfbooBfKGKyZFcLDI5EiZ05bAMhXD+fYGELq964VsQT3wgiBbdZ3zz9B7oXSc6HnSx/cPsruDqc7WLzINn7fSa3MM7zwne7wwmxyHd09bNlJMfrR6aKvFplTl+hkYXyvmBhqDPpBvIXt16QXmtTKoOe+7mOWR3fc+vpsXqYM2JdC+ZJUv5PeDbuTZXmM7rsd0Nn3VrV+uLatq/AiFBey6DZXYEgWA0zp9vKkRiKf24UfA77coOQUgLZmXR+utb00dyPFGUqyorY5LgmpFNDtaPNj2pJuOgwARKSKDjuyx2vFixIAWM+58460erSBFRahPQU/yMcBCe9Xqgst0bd+hWJpfxKRUoUx+EyMUI6LyZSSkhaICHUh1wAJ6H/VXGBm1poBUgUJlDVxEqEz6ywrytK85oO1Zh+gKk6AvFEepxy2W1rJImvapLEzbeDarJIg2YEGAB4MB9J91MRJH+cPFeVAz6ZgrZkLFXZXLOhEuQtgYf8OMpKpv1Y8s+TIxy9DLiqXX/E2ZmZd1wQ1GK13+8TLhPCaHQEYDEeTagZmripdtmJZklIo9PKw4cgg8a1fkRdWZ+TSyU7tsGAP5Jl1XYMxXF8jSr9NlUAvO9bcgenBWBGhKJVSkkG2bhXhseC4ZV5IkB2rtS6dlangPpZCMw6M3R3WWrPWdTVVg9H6xmbYAy2QBn52gK6r6eSAwFAF+Sis7om27B8bWyyMVjSf1unuWDBSCh+isCWuwrdl52QSGhXgGbTWuhyORusbPda0CMrMCLPJQV1Vlu1k3jQJSUy2wvzVGmS/5UL2lJdWFE5hvl4S2bbMGkqRdkdaLUoSg4nUaG2jHA774AuLoszNR1fTST2badZB6HVfIkb0c4Wjsqy+DElLXT2z3Z3BDSIYaLYHZqUUKSrKwWA4KoejnsiyC1gCZdFs+0qurCkQ95a55I6BjLtZEA4TZC1jMPq1//8CVvYhjj8/8Bco6wVSQen/3fL8tup2hCzU1aL93LkOu/tJw1iSkI25g3Vowtlr+6B+buO2HnoeYHf00NatXGYaxhJF9IrLEu25G60dLZPO28p7Limv0Pfrp+cSEuMcTYlJ+TjCaG3UeOapbVod+Z7kRuKRq+ZisjeyaRt1TyzBWhup+sz/ATefof5JBRJjAAAAAElFTkSuQmCC" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "AEON Labs", "NodeProductName": "ZW122 Water Sensor 6", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Notification Sensor", "NodeGeneric": 7, "NodeSpecificString": "Notification Sensor", "NodeSpecific": 1, "NodeManufacturerID": "0x0086", "NodeProductType": "0x0102", "NodeProductID": "0x007a", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 4} +OpenZWave/1/node/36/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/32/value/604504081/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 36, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 604504081, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/value/281475585687570/,{ "Label": "Air Temperature", "Value": 17.100000381469728, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 36, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475585687570, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/49/value/72057594655293460/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 36, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594655293460, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/618102801/,{ "Label": "Instance 1: ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 36, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 618102801, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/281475594813462/,{ "Label": "Instance 1: InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 36, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475594813462, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/94/value/562950571524118/,{ "Label": "Instance 1: UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 36, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950571524118, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/562950567624724/,{ "Label": "Waking up for 10 minutes when re-power on", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 36, "Genre": "Config", "Help": "Enable/Disable waking up for 10 minutes when re-power on (battery mode) the Water Sensor.", "ValueIDKey": 562950567624724, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2251800427888657/,{ "Label": "Timeout of awake after the Wake Up CC is sent out", "Value": 30, "Units": "seconds", "Min": 8, "Max": 127, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 36, "Genre": "Config", "Help": "Set the timeout of awake after the Wake Up CC is sent out. Available rang is 8 to 127 seconds.", "ValueIDKey": 2251800427888657, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2533275404599316/,{ "Label": "Current power mode", "Value": { "List": [ { "Value": 0, "Label": "USB power, sleeping mode after re-power on" }, { "Value": 1, "Label": "USB power, keep awake for 10 minutes after re-power on" }, { "Value": 2, "Label": "USB power, always awake state" }, { "Value": 256, "Label": "Battery power, sleeping mode after re-power on" }, { "Value": 257, "Label": "Battery power, keep awake for 10 minutes after re-power on" }, { "Value": 258, "Label": "Battery power, always awake state" } ], "Selected": "USB power, sleeping mode after re-power on" }, "Units": "", "Min": 0, "Max": 258, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 36, "Genre": "Config", "Help": "Report the current power mode and the product state for battery power mode", "ValueIDKey": 2533275404599316, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/2814750381309971/,{ "Label": "Alarm time for the Buzzer", "Value": 1968650, "Units": "", "Min": 655360, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 36, "Genre": "Config", "Help": "Set the alarm time for the Buzzer when the sensor is triggered. 1 to 255 Repeated cycle of Buzzer alarm. 256 to 65535 the time of Buzzer keeping ON state (MSB). 65536 to 2147483647 The time of Buzzer keeping OFF state.", "ValueIDKey": 2814750381309971, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/10977524705918993/,{ "Label": "Set the low battery value", "Value": 20, "Units": "%", "Min": 10, "Max": 50, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 39, "Node": 36, "Genre": "Config", "Help": "10% to 50%", "ValueIDKey": 10977524705918993, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/13510799496314897/,{ "Label": "Sensor report", "Value": 55, "Units": "", "Min": 0, "Max": 55, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 48, "Node": 36, "Genre": "Config", "Help": "Enable/disable the sensor report: Bit 7 - Bit 6 - Bit 5 Notification Report for Overheat alarm. Bit 4 Notification Report for Under heat alarm. Bit 3 - Bit 2 Configuration Report for Tilt sensor. Bit 1 Notification Report for Vibration event. Bit 0 Notification Report for Water Leak event. Note: if the value = 1+2+4+16+32=55, which means if any sensor will report alarm.", "ValueIDKey": 13510799496314897, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/13792274473025555/,{ "Label": "Upper limit value", "Value": 26214400, "Units": "", "Min": 65536, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 49, "Node": 36, "Genre": "Config", "Help": "Set the upper limit value (overheat). 0 Celsius unit 1 Fahrenheit unit 65536 to 2147483647 Temperature value. Default: 0x01900000 => 40.0C", "ValueIDKey": 13792274473025555, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/14073749449736211/,{ "Label": "Lower limit value", "Value": 0, "Units": "", "Min": 65536, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 50, "Node": 36, "Genre": "Config", "Help": "Set the lower limit value (under heat). 0 Celsius unit 1 Fahrenheit unit 65536 to 2147483647 Temperature value", "ValueIDKey": 14073749449736211, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/16044074286710806/,{ "Label": "Recover limit value of temperature sensor", "Value": 5120, "Units": "", "Min": 100, "Max": 4080, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 57, "Node": 36, "Genre": "Config", "Help": "Set the recover limit value of temperature sensor. Note: 1. When the current measurement less than or equal (Upper limit - Recover limit), the upper limit report is enabled and then it would send out a sensor report when the next measurement is more than the upper limit. After that the upper limit report would be disabled again until the measurement less than or equal (Upper limit - Recover limit). 2. When the current measurement greater than or equal (Lower limit + Recover limit), the lower limit report is enabled and then it would send out a sensor report when the next measurement is less than the lower limit. After that the lower limit report would be disabled again until the measurement >= (Lower limit + Recover limit). 3. High byte is the recover limit value. Low byte is the unit (0x00=Celsius, 0x01=Fahrenheit). 4. Recover limit range: 1.0 to 25.5 C/F (0x0100 to 0xFF00 or 0x0101 to 0xFF01). E.g. The default recover limit value is 2.0 C/F (0x1400/0x1401), when the measurement is less than (Upper limit - 2), the upper limit report would be enabled one time or when the measurement is more than (Lower limit + 2), the lower limit report would be enabled one time.", "ValueIDKey": 16044074286710806, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/18014399123685396/,{ "Label": "Unit of the automatic temperature report", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 64, "Node": 36, "Genre": "Config", "Help": "Set the default unit of the automatic temperature report in parameter 101-103", "ValueIDKey": 18014399123685396, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/23643898657898516/,{ "Label": "Get the state of tilt sensor", "Value": { "List": [ { "Value": 0, "Label": "Horizontal" }, { "Value": 1, "Label": "Vertical" } ], "Selected": "Horizontal" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 84, "Node": 36, "Genre": "Config", "Help": "Get the state of tilt sensor", "ValueIDKey": 23643898657898516, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24206848611319828/,{ "Label": "Buzzer", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Enabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 86, "Node": 36, "Genre": "Config", "Help": "Enable/ disable the buzzer.", "ValueIDKey": 24206848611319828, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24488323588030490/,{ "Label": "Sensor is triggered the buzzer will alarm", "Value": [ { "Label": "Vibration", "Help": "If the vibration is triggered, the buzzer will alarm.", "Value": 1, "Position": 1 }, { "Label": "Tilt Sensor", "Help": "If the Tilt Sensor is triggered, the buzzer will alarm.", "Value": 1, "Position": 2 }, { "Label": "UnderHeat", "Help": "If the Under Heat Temperature is triggered, the buzzer will alarm.", "Value": 1, "Position": 4 }, { "Label": "OverHeat", "Help": "If the Over Heat Temperature is triggered, the buzzer will alarm.", "Value": 1, "Position": 5 } ], "Units": "", "Min": 0, "Max": 55, "Type": "BitSet", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 87, "Node": 36, "Genre": "Config", "Help": "What Sensors Trigger the Buzzer", "ValueIDKey": 24488323588030490, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/24769798564741140/,{ "Label": "Probe 1 Basic Set on grp 3", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Presence/absence of water 0xFF/0x00" }, { "Value": 2, "Label": "Presence/absence of water 0x00/0xFF" } ], "Selected": "Send nothing" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 88, "Node": 36, "Genre": "Config", "Help": "To set which value of the Basic Set will be sent to the associated nodes in association Group 3 when the Sensor probe 1 is triggered.", "ValueIDKey": 24769798564741140, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/25051273541451796/,{ "Label": "Probe 2 Basic Set on grp 4", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Presence/absence of water 0xFF/0x00" }, { "Value": 2, "Label": "Presence/absence of water 0x00/0xFF" } ], "Selected": "Send nothing" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 89, "Node": 36, "Genre": "Config", "Help": "To set which value of the Basic Set will be sent to the associated nodes in association Group 4 when the Sensor probe 2 is triggered.", "ValueIDKey": 25051273541451796, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/26458648425005076/,{ "Label": "Battery report selection", "Value": { "List": [ { "Value": 0, "Label": "USB power level" }, { "Value": 1, "Label": "CR123A battery level" } ], "Selected": "USB power level" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 94, "Node": 36, "Genre": "Config", "Help": "To set which power source level is reported via the Battery CC.", "ValueIDKey": 26458648425005076, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/28428973261979668/,{ "Label": "Unsolicited report", "Value": { "List": [ { "Value": 0, "Label": "Send Nothing" }, { "Value": 1, "Label": "Battery Report" }, { "Value": 2, "Label": "Multilevel sensor report for temperature" }, { "Value": 3, "Label": "Battery Report and Multilevel sensor report for temperature" } ], "Selected": "Battery Report and Multilevel sensor report for temperature" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 101, "Node": 36, "Genre": "Config", "Help": "To set what unsolicited report would be sent to the Lifeline group.", "ValueIDKey": 28428973261979668, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/31243723029086227/,{ "Label": "Unsolicited report interval time", "Value": 3600, "Units": "seconds", "Min": 5, "Max": 2678400, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 111, "Node": 36, "Genre": "Config", "Help": "To set the interval time of sending reports in Report group 1", "ValueIDKey": 31243723029086227, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/37999122470141972/,{ "Label": "Water leak event report selection", "Value": { "List": [ { "Value": 0, "Label": "Send nothing" }, { "Value": 1, "Label": "Send notification report to association group 1" }, { "Value": 2, "Label": "Send configuration 0x88 report to association group 2" }, { "Value": 3, "Label": "Send notification report to association group 1 and Send configuration 0x88 report to association group 2" } ], "Selected": "Send notification report to association group 1" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 135, "Node": 36, "Genre": "Config", "Help": "To set which sensor report can be sent when the water leak event is triggered and if the receiving device is a non-multichannel device.", "ValueIDKey": 37999122470141972, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/38280597446852628/,{ "Label": "Report Type to Send", "Value": { "List": [ { "Value": 0, "Label": "Absence of water is triggered by probe 1 and 2" }, { "Value": 1, "Label": "Presence of water is triggered by probe 1" }, { "Value": 2, "Label": "Presence of water is triggered by probe 2" }, { "Value": 3, "Label": "Presence of water is triggered by probe 1 and 2" } ], "Selected": "Absence of water is triggered by probe 1 and 2" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 136, "Node": 36, "Genre": "Config", "Help": "When the parameter 0x87 is set to 2 or 3, it can get the sensor probes status through this configuration value.", "ValueIDKey": 38280597446852628, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/56576470933045270/,{ "Label": "Temperature sensor calibration", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 201, "Node": 36, "Genre": "Config", "Help": "Temperature calibration (the available value range is [-128, 127] or [-12.8C, 12.7C]). Note: 1. High byte is the calibration value. Low byte is the unit (0x00=Celsius, 0x01=Fahrenheit). 2. The calibration value (high byte) contains one decimal point. E.g. if the value is set to 20 (0x1400), the calibration value is 2.0 C (EU/AU version) or if the value is set to 20 (0x1401), the calibration value is 2.0 F(US version). 3. The calibration value (high byte) = standard value - measure value. E.g. If measure value =25.3C and the standard value = 23.2C, so the calibration value= 23.2C - 25.3C= -2.1C (0xEB). If the measure value =30.1C and the standard value = 33.2C, so the calibration value= 33.2C - 30.1C=3.1C (0x1F).", "ValueIDKey": 56576470933045270, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/70931694745288724/,{ "Label": "Lock/Unlock Configuration", "Value": { "List": [ { "Value": 0, "Label": "Unlock" }, { "Value": 1, "Label": "Lock" } ], "Selected": "Unlock" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 252, "Node": 36, "Genre": "Config", "Help": "Lock/ unlock all configuration parameters", "ValueIDKey": 70931694745288724, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/112/value/71776119675420692/,{ "Label": "Reset To Factory Defaults", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "Reset to factory default setting" }, { "Value": 1431655765, "Label": "Reset to factory default setting and removed from the z-wave network" } ], "Selected": "Reset to factory default setting" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 255, "Node": 36, "Genre": "Config", "Help": "Reset to factory defaults", "ValueIDKey": 71776119675420692, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/value/1407375493578772/,{ "Label": "Instance 1: Water", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 2, "Label": "Water Leak at Unknown Location" } ], "Selected": "Clear" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 5, "Node": 36, "Genre": "User", "Help": "Water Alerts", "ValueIDKey": 1407375493578772, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/113/value/72057594647953425/,{ "Label": "Instance 1: Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 36, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594647953425, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/618430483/,{ "Label": "Loaded Config Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 36, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 618430483, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/281475595141139/,{ "Label": "Config File Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 36, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475595141139, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/562950571851795/,{ "Label": "Latest Available Config File Revision", "Value": 10, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 36, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950571851795, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/844425548562455/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 36, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425548562455, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/114/value/1125900525273111/,{ "Label": "Serial Number", "Value": "0d000100010108010100000004030800000000", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 36, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900525273111, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/618446868/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 36, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 618446868, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/281475595157521/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 36, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475595157521, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/562950571868184/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 36, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950571868184, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/844425548578833/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 36, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425548578833, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1125900525289492/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 36, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900525289492, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1407375502000150/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 36, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375502000150, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1688850478710808/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 36, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850478710808, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/1970325455421464/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 36, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325455421464, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/2251800432132116/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 36, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800432132116, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/115/value/2533275408842774/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 36, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275408842774, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/128/value/610271249/,{ "Label": "Battery Level", "Value": 100, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 36, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 610271249, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/,{ "Instance": 1, "CommandClassId": 132, "CommandClass": "COMMAND_CLASS_WAKE_UP", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/281475595436051/,{ "Label": "Minimum Wake-up Interval", "Value": 240, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 1, "Node": 36, "Genre": "System", "Help": "Minimum Time in seconds the device will wake up", "ValueIDKey": 281475595436051, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/562950572146707/,{ "Label": "Maximum Wake-up Interval", "Value": 16777200, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 2, "Node": 36, "Genre": "System", "Help": "Maximum Time in seconds the device will wake up", "ValueIDKey": 562950572146707, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/844425548857363/,{ "Label": "Default Wake-up Interval", "Value": 3600, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 3, "Node": 36, "Genre": "System", "Help": "The Default Wake-Up Interval the device will wake up", "ValueIDKey": 844425548857363, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/1125900525568019/,{ "Label": "Wake-up Interval Step", "Value": 240, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 4, "Node": 36, "Genre": "System", "Help": "Step Size on Wake-up interval", "ValueIDKey": 1125900525568019, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/132/value/618725395/,{ "Label": "Wake-up Interval", "Value": 3600, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 0, "Node": 36, "Genre": "System", "Help": "How often the Device will Wake up to check for pending commands", "ValueIDKey": 618725395, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/618758167/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 36, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 618758167, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/281475595468823/,{ "Label": "Protocol Version", "Value": "4.54", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 36, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475595468823, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/1/commandclass/134/value/562950572179479/,{ "Label": "Application Version", "Value": "1.05", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 36, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950572179479, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/,{ "Instance": 2, "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/,{ "Instance": 2, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/618102817/,{ "Label": "Instance 2: ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 36, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 618102817, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/281475594813478/,{ "Label": "Instance 2: InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 36, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475594813478, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/94/value/562950571524134/,{ "Label": "Instance 2: UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 2, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 36, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950571524134, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/,{ "Instance": 2, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/value/1407375493578788/,{ "Label": "Instance 2: Water", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 2, "Label": "Water Leak at Unknown Location" } ], "Selected": "Clear" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 2, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 5, "Node": 36, "Genre": "User", "Help": "Water Alerts", "ValueIDKey": 1407375493578788, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/instance/2/commandclass/113/value/72057594647953441/,{ "Label": "Instance 2: Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 2, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 36, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594647953441, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 5, "Members": [ "1.1" ], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/2/,{ "Name": "Send the configuration parameter 0x88", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/3/,{ "Name": "Send Basic Set when the Sensor probe 1 is triggered", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/36/association/4/,{ "Name": "Send Basic Set when the Sensor probe 2 is triggered", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/37/,{ "NodeID": 37, "NodeQueryStage": "CacheLoad", "isListening": false, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0371:0005:0002", "ZWAProductURL": "", "ProductPic": "images/aeotec/zwa005.png", "Description": "Aeotec TriSensor is a universal Z-Wave Plus compatible product, consists of temperature, lighting and motion sensors, powered by a CR123A battery. It can be included and operated in any Z-Wave network with other Z-Wave certified devices from other manufacturers and/or other applications. By the built-in motion sensor, an alam will be sent to the gateway when the motion sensor is triggered.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2919/TriSensor user manual 20180416.pdf", "ProductPageURL": "", "InclusionHelp": "Press once TriSensor’s Action Button. If it is the first installation, the yellow LED will keep solid until whole network processing is complete. If successful, the LED will flash white -> green -> white -> green, after 2 seconds finished. If failed, the yellow LED lasts for 30 seconds, then the green LED flashes once. If it is the S2 encryption network, please enter the first 5 digits of DSK.", "ExclusionHelp": "Press once TriSensor’s Action Button, the Purple LED will keep solid until whole network processing is complete. If the exclusion is successful, the LED will flash white -> green - >white -> green and then LED will pulse a blue. If failed, the yellow LED lasts for 30 seconds, then the green LED flashes once.", "ResetHelp": "1. Power up the device. 2. Press and hold the button for 15s until Red Led is blinking,then release the button. Note: Please use this procedure only when the network primary controller is missing or otherwise inoperable.", "WakeupHelp": "Press and hold the button at least 2s until Red Led is on and then release the button,device will send wakeup notification to controller if device is in a Z-Wave network.", "ProductSupportURL": "", "Frequency": "", "Name": "TriSensor", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAMIAAADICAIAAAA1GKkAAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nJx9abPkuo3lAaXMvGvVKz+72zE9MTE9jvn/P6j7S4fDjvZbarlb3pREYj6AREIApSqPwq6nq+QCAocH4CKK/vrXvxIRAGYGIPelFLmRJ/qTTSNZ4q96ya9YX5pFK40Z3RP7p80VK9rK1S0n3scsW79aDcT0VjAr84+ndFmixtBTXVd+TWkLcYXbZF0VdRVi/0wRQ8ycUpI/bZ4ICyeZ3Lg/nb66JWhd3YpigyWXPHdNirqzJds/f0Ra+8TpV65YuMvoDOPk2anFldmVM/bqnRapulR1Vlf66xbOtmqRLKOr0trGqcldEeM21xaQhedimd26Ygf6rtW7xnA6EjFi7Qi2iS2KUI5id1mw2+VsvVH+WKnN4rp3t8k7OtwyUxQeG0iy5Y9RTVsepMuN7uriAGuld2uJatpiha3LatxpzRkyalzY1z53pe100y2IxLY4iOy0iAzdRuV0VdS9j9p25WADN1Z496fLJTcpViM63ZKp+9wVutVjrEzYuCh4gf30jjhdjVGhtuQtZHQRrHZ19LMDiG53t5V222Ur6or3g+VE1GINLDLXVvmO1Vwf0LxJE0WbqYViN926XMudxm2ySEL/bCGuwOiMrOeK3TFaywIipeT6a1dF3bbvNNNVtKMcJ0y3sbHeLY6P1UVS+G45EXCqmeSyuTzO00WasYXqT7GD7oDmRxq5czkA2dL2C9zpeVj3pajfCHT0esKWsW1pW1zikrmbCIItYuvWuF8dQgNtLveTXAkBzk5xNttWN2Jz7VRmsczrYCKi4buc1NWp5Q8Hi9ioaAxXzo+0FBvW+u4T9ODeTdO9/8Gry6k7InVr32FNKSepRiyAHFx4HWeoENGQkQO2yDMi8rsUsoXvrWZbX0DbjkAL71ZkmcDqwemXe8OOH7HTjhv6p65ob14PaCxBRrJw5fz4c/kpxeopuLCuglQsC3PXU3l3VNl9LgGN3DiXsUXdW9cO7Bw4bC3d9toSflASC7idxF287rRo58kOubqHO5j4p5hV7dgflGk6N78SpbHa1ye2nC0yx4ZdtRCdAnU9aYvMulV0+dImc9FrLMFeOyDr9njN9V05tRN2G7XT2C0duidbXWhLnu5NN40gZDVS+8F+YInRNi+iMD7ZupwZtOT9zuG4xFYae0xXki3GUl60KnPidWWzxnY9yrXXPq/GSMkJH5uwxa/da98TRbDudOytMuXP1Ugtms3S+H7p2DCqyvddOrViOEl+JIDYqkWfRKt3S+tSQiTLrc5qRdUQxGqvCxQy8UCXe3ZEjVfX0nrfnRekEJC45u90IdIBvy0OBj07PaDLdd/tH1sdy2aPNtsq57uVOpN3n7hytswcC49z3zuJrbW6tce2dDvSVvqtJ+7qwhe9oNtZwZnM3aSt/hFb5drQdXOxAUotkca2FErrMSp29bVlRXUQXbaIVTsJtcvGlN8lCVvRd/uVg69rY6yoy3bYNYGTZCsUiX/GSmMD5d+kkSwM6LrEHhu5hRtXWTf+0MbvMDOtmdaCb0fCbuBC5lory4u0/tMitf6JbZN8V35nJNvBLCt0FeIU2K2uq1XbM63kW7buQnZLDLKxEQystgq1KbuIjm3owllwoGPALRtE7bgSXC3dpqJDJyqegqNfiKQykQTLj9r22D2iqOsCr5bb0pLqcEsbMICzz38kS+zMXcp0JW8p1l7X6UdXrkOrVlCzmdH4VgeKlt4S1P30HYnNalfUo0V2tCgRdKosUAszfJlaQFfaHf06ZFiRXK6UklNgt9/qc0st6FlKf9rxRGSunZRkvNNOL2Xm0Zkkgin2Ffd8vwJnaVtdt9lbpTmq6/bLKJKrhZkLl2ulJJxDICYQSd9Yt0Ar2mpvTNBVhdXAFqlstdqW020jjBodkWz1N5femSZW6qzmJBydpiJErJljg7s6xQZo7L9daaJG9vuWy8sCDPFWjFxKLnnJOec5l8LMXJpLYwYxmEDMDCKAiRIIlChRSodhTETDOA5DEhb7QXu7ZEI2W03e6u5b6HGQ3bKIdmDX61yWSHKuKAcdhyFqmxABjFudLILALVFtNQM9HnIt6bY8/urwZCEeaZIBBi+5LMs8L8uSl1wymECFGWCUUgoX4SNGEUih2RGcKssncdmUCBJWE9E4DMOQxmEch2EYhiEN1q27tkRLoNkp7rrs5trqfjZlN0uXY7ZK9grsuYWIyJgewIj1pfi6dnFT0Jay3OXozWktqt51C4sVm6CXt2YsXC7TdJnnJc/yuHDJORf5hzMXLkWSFyAxFwarR2NmIFWKAgACCy8R0SAENQyJBDmofw7DOA7D4XA4jIeUUiIwW68B23R4PhPq7HdFVUgpRfkspnFkFn2IU74DorOXKyfylrOO/XNUceW3lJJ9LSSSylah0bl2O0SEo+t2NmNsZ9T1kvNlvkzTLKlLKTkv8zLnhbmwRiDiwlqZDDExAUgAg4okoqtTJGbOzJBYCgCYiCVCH4ZDSkQ13CeAhiEdD8fjOI7jOA7DMB6I2mCvtsCt9tQ2idkipbneaxEjniFyj9PM1rXjE13/d0QYLag3lY2sWDFnfB6JUR5KOVZcJ4ETJXYaV6Mqrqap3b0AlEt+ny7TPDMKA/M8z9OSS+bCIFaYQNwdCAClBLSNA5B7gICCmpol3K5aYRQQgxOhgAggZjCXnC8EMBMIKVFKw5CGaZ4SDURE4DQMh/Fwc3NzGMdxGCkMo4g60W60U/wT62Gy/Tdm6ZKHQ4AaTtcQtzptF+715m9/+1vMEzNHnux2hS797pfvxIqOL+qoubCJueTMl/l9npZSCsBUo2GgkMTbVyoCy+8s6AEIBAIXbu7lSh6AuDkQ1TIqt3Cp6AQxylU8ZgioUkppGEbxckJdaRzHm+PpdDyNw0i0SecwPcfpNroY5wQihUd9dtO4K2Jly1j2ur5g5ODp/KjlWP3JZYmV7binqLXoE63jr/UySilvl/d5WXJZpmmepgujgNOVflgiGwhuGhGxjVYIBGKw+jdBWGUNkihHcjUwQhISidcjAJyYhfmozR0gl1w4z4sQ3jCOg+Dq/P4uMfzxeLw73R6OhzEN0V9Eve2QutVVV6XRIXaZKVrZBjZOhq6rHR1ZdVGvVuz2ANfyyJyRySKebNsciO2TwuX1/bzkPM/z+3QuuTRaYQAoVP+iwlwHWRVSDDClJFsySCKimouJkJiKkBNA4EIVSoJLpkSVz5AYWQBHSEQSjidtM9UZhMRcmHleZrDMJZAM8grn8/lCiQ7DcHM63d3eHsZDzdmIrgsRauPlSNgRGVtPLDIcSiIRbDnHjsdwTi1W7DAbXVv0Yl3pnRw70I7liE1LKa/v53lZLtM0LzNyZiESYnCqXolAlLjIFECWkZw8LlwSEaOAIZG10BWAykzyuBAzC/KoesMqNZlJfwm/6j2bddwksTyaJy0mGRgE8JDGYRxSGlIiopQS3d3c3t3cjuMwpAEblyOJHb1tadgWZVM6hX+35I6B/va3v3Xdh8NHF5Vdibs/7ZSz4/LswyUvr+fzkvNluizLXDhLnMooLN2dJVxW+SsJMYv1FCnVJ1Fizo1+Wq2m9jq13aBWWGCEGobXSAm2UpgYSgoiGQM2lEt7ikiVKFHCOBzSkFIaiZASnY7H29Pdzek4pIGImAtRirZcWZEg3cbqPKrXebF9nDljxZJd9tHVhPWY3/qXHSShB/AtxrKo1WRdr1p/Ai85n9/f5zxP87LkhZkIAxfxSjJhI46EpESZn65eglicXaLEXOQXoYoa53BBIjBoIM6AUEblEBbPyOIIW7BdnShlsIRFRCQhFjFQHaDE4s3SuEZZ1SECKBlTnkXa8ZCGYcgln98vRHQ6HO5v74+n45jI9AFQ6LHNF3cM4RTuQBC9mLWgxjBbhrPX6AzpgOkodKtbaHZXfZQjChGdrvtpyfl8eZ+XeZ7nvEjsKmaVkT8zJSBXfQqEqv+hGhMRuDovQz1CRGJTJq4cxM0mVKN0ZqaCiiGgrvMzETHVWSPmUvdKUEEhbqBhbhBjcC1Z8At1xPVX5nle5nkhzMOYxvHAhS/TnFI6Hg53tze3p9uu9iJorEr3A5qdq+tVXDhl78fubzGOcwQTJXbVd31W7BNRJvfnvCzn6SIqXpaFGRCeB5PEMUXi6gFtjrA6lgJKDFnPr0AqwgFg5gISzCExGEljJQUBExNTkVqA1Ca4Ux3OocGCm7MDgIHoGmsTErcJApOGqlusc1GNZpDEQS/zMk/LMNI4Hg7jkZkv8+Upvd7d3Nzf3o2jHxVFi2wZqEtLXTtuFas2dQjpzBttcQM2YN6tstsJbDOifN5zg+a8vE/TnGWZQ8i/xre1u2uMXCd4xCqyhl/H8dcBF3Bd0q8hTQGImSDDNIask7QBvUonzFFQox+q7q7CqNTEfA3VuQ28mJkA5urzqiOuaTIRscROKNKQGpsDzIWQQBiH8XiSCSdKiW5PN/d3d4fxIM7BhRxbpunGD+6Jy9gFq7Og/np1app5K0xRh7UlqGuSrS9SYheF1nUunN+XacnLLPFQtSxB3U6LeQl1jb5ZiBpEEqk9a3XVz0jPBydQIYAwyNJJhScVZp1FqA0ozGJXMEG4kAEUiZyq2xKaVJi2GQh5zhJxtd/arEP7G0mGdaIJruE8z/O85HwYx9PNDSi9nt/Pl8vD3d3D3b3q1unQKdnhZss3de24FeC6Qvqb0qNA+lBfR7TJbDzURbEVzj2PbSMgl/w+vy95mZZpyYukBai9LY4aVjITadhDSTwViICEhIb7RFqXVFeIKaVEiYDEgIz7AAIRJVAiSqmVyKAEKiklaoF5C4qAVNmwTkLWIiR+kvoTkbAQy3Cv6kCnEYhlea7G3QTpRkSoazQA8zIv0+vLy/v5wlyYy8vby6+ff5vnuXY8rIzqtGpN496asveRIJQ1LC/Ei8xWwFWhDhnW3rEyV431TVaIrnB6b8camfl9nnMuy7TkZeGSASRQqoMi6ccEJKKkNhXmSBVFYIDqLjQNVuojAND1EKIhUaJRWivjK6IkkIRUVAu4Du7q4IiIkKjF9lUlVNrE51VLKQmJEcmCXeUeibbrbHqN1Vo4B4a0rs6mFzCXy/T++vYyzRMzMpfPT1/fzmcRTRWr7zI46zi4qKW2Di7T7DGjhUptoPvbFWENDwMmW5ZNhgBBl3cLZGoHZp7mqTDPc16WBSDGAEaiVFetSNrG9f6qwra2Udc1iKp9k/QWsWzb4SjuqTQlFGZGoqQRNIuEDGJKLHBF5ZnauBZOCROmas6GaHBSxmQ0U1UEVphQBQ6DJMQv1WnqZoOrtokZzCglv5/f387nvJTC5cvzt5e3N1WdXtEi7sb9G92Ci22o52GuMHJV2hS27sgoKndEyZb3tQJFmeQ6z9PCZZnnZZkkFRGIZBZP4hwZBAmG0NwB2qo+FJQMmU0mjXoVwC2gGio3kKykUvWXNXqXYgbQgOplKkaVnWqsBkFkc58tiqtNI50EIap0SdQWjokGYn07Z0DtIU3/1HiqgpO5UCm8zPP5/JZzJkrPL89v57Prug5P0RZbiHEmdsCwT2wt17d9u3gi41+toF1a0mQKJt7YM6mFuGIv88TAsuR5npnbPBBYiYC5OTGSzVysnNOCEgINDAJKopQSgzlB9+Jx9SrEKRGImahuS6ockqu/SokErlxDeotRMMmWW6RKP6Rq0eUKqipN4sIqb5U6rcWpwUQaqUOHei9hewNbjQiZmZEZhZlyLm9vb8sy05A+f/s6z7OzNDUHp8Z2+t9iJndFbKh91XzXtywi3DS/Y8tuZTYXmSseJuH4idsQ/rLMBcjLMs9TkecsEU4Cc1LHJ0onUErc3Je4uCQrqGBQW0OQfkKpWVoj2xrokkTBbW4arfsDhZFBpW5fKyi50UKTvEbBlBtHou5iq3oQKILr/xtHEYQ+qXpzpkaIAKc0kDr46vJgw7s69QWW3cDv7+d5mVNKX79923IFrq86K7hcjix4HRtFo9euwut4uWtsLd3CMKaxf8qxMrZwu6Mtctucc+GylHmap1IKmAHd2QOSwKg6HQg0JGBS30NEpc3IND4ACEWBWq1dGAAl2ZIt4RSjbisBExcBcLVenexOIBrKFYPQyW4SnEs5LJgUlLSoTWaOKuFRc6PirQUa6nbRnC9f6+FanQwsuM6K1c5Zcrlc3kspz+eXXHKEi7WOswgCMiTB1oZxd28JrA74IyzU0vahO/LC+ThsgFp/shXblJk5l5yZlyXnnCUgIiEXDWPkvY06hoH9l64cU10BcWI1USWIJGFrojQQJYAge60hw60k/jGheiuWKUeQjBFJvCTVFQyAq28iLmgeCo02WvjTBgV1KkJCrjo/kUiC+ToXQC1oQ/s1XUtCArE+AOoijVzLnKd5Jkqfv3xm5upNeywge+r2DeRAZv90EFwBA2tIWkBUvmp1OyqKUnYh4uqzAum/c14YnJd5nsTBU42aZTRGLbwlGeXXEVAdiCuUwUmgV7EgdCEuo8nPdbzDsrO27sBmZjBSoz6AiOp7RQNBB1ZAXSUBg5FKm1uiK8fwFScyxEMqADMnwtAUBaqhUmpwkT133EBSA/0KTDTyYxC3GVGCccK8LDPALy8voomodhucOPtaC1okRRfpbG1/vZ6LDRNQq4ElkZ2z0spsQY60HM7sPl/XsMJ8WWYA8zxP0yw8BJQaFNQwoC2TNcUWFpwUkr1ERNe9rUTkO40YQzYbyTo/CnMicC2BWvRKzIWqyyD1QPJQ0MglN7GKAoYBcKpzThKvy3+YWkiEurWXsAY/tDvU1FWauq5LNFSve8VTYg3MmIlSQeZS5nkp80K9SDde0f9EO7onXVRdIxYYfOjTSGXdWqMv60ZLVmjn9eY8I9GSl3lamsFLo7UW0RK3aEhUabzdlahqAhCLJRt5IFGdNpLSuRSSNQeiulxKQA3FmVLzc5QSpYYh4ZtUZxCJGBkVeLIB/Brn4Cp3Ip3PlsRok6QNTpqlKhmogwQZrlVN4Dp/RCyrN9WrgWUxjhnTZdoKXNzlXEd0bc5kLkG3zOtoXIJii0dHPPaJvbHgc1OoUXr767wsDMo5z/Ocy8I1zYAWBbRheJKZ4+rhUPu6PEgSGtXhm0BGYpprZcxMoIEoIRElhixQtBwCvUTCGG1HEnRyGWiTQ5ABoOhtAGQRrY3skVBnjq6bKiXsZ0hJ3HBTB2kVQCBq0w8ly0MCcxoS6hIcqjjKz0lHbQwUUHl9exmGoVnwaqxuRKEWd/Rjg40t9HTZbnXwVgyc7b3KZOuzWaIDjpDSX3MpmTMzL/M8LzPae0BVcXWFpLINJYltm3Fb6bJI0ILwFg1pvYQW2aL9WdEn6wxUh3+c6oZ/JFmMAwgFkEmqRCRhFFMd+MnUUKFELEwkcVSCpGRqiyi1HlToEyp8tWGo3FkllUi9tkUMLKMLbqM5AHURkWTiuwBIl/f59fWc6obJpsgedNyf9ia6ox0ecu5odOksICwbxSJg8K4pHRBtyXbthiWsJpqWeZ7n2iWRVoXX/8sySF2EAHTZndoWeyLiulmsvggiUc91l3WttA3rqD0tBEIpMuZqFgYYSIVBbb1dQt+6C4VKqq4woUB2lYj3q9E16WJI3enBanIxfxq4tI3aiaiwTFlWgrrOEwgnaTxUNAxPCUiJCzO4cCmZn56e8rLIVqSoefvQ4cba2uZSs1KIlvpeKDKHc7GWXbRKB1tmdu7Mioj1B9oAzMuChFzKMs3cpol1QbtGCTIEklBWImi+xtl1zkfYpu26J0kOgDhRXbeg9lOqAyLU8RRbXVGbf2LxMOIYy/VlbJlKTNCeriur7bUTiawZlFJqE9B1pqjBvxRViZAX12hc/CczErUXcblFTwBzoTb3LTMIbWWEufDL68v5fGbWbQWdSNSZ1WLFPVHbOfw5yzp4+VDGeS7nsyLL7Qsd0QZgzgsTl5Lnacq5vo1aVxba2KdVUP9XQ9dEMqnDsrJKdSIGbUd0JYk6DdMITRyEyN/WTrh6vBbEo0hMIwN9MBMzCEObRmYS9yOBNgjgSq5J5s+5Dv0p6WprqhEP2osARESMJGt9dYBYp72VfAuXgrahV8I1gKkNBaj2xsKZmUvh17fXl5cXZmbwMAzWwNrbd8KayBFb9rVPrE3lpnMqtK3b0pqjuC6wdqSUayl5ybkASy7LskiMidTMSSZigVhPQg6JQOusM7XIk+tifmvFlZ/a7y0ikjK4xrfVrLI4p8SmfR+oe5dYvVmdsoKwZN1cy8yCrnrKhLh1oUMN7nX6PdW8sjhybQvJK9syoKgolzY2liMWyNZCREBmfj+fX55fSy4AuFQYqbGtdbYiE+tYusOjyD3x1OXKRrYszckmiHZO1BJVV8oojaaf5plSyrnILBG3SZu6yoQ2vqoj+jqV0hwDSELelCilFtHUgLUarA24a2Ii2ZLd+qOELJzQ9vDrWkqtk9tQqr02gDaXzQywLO0mDA0rYnpuSx4s294qKGUzCDPkfW9GXQORaXMU9a0yq1bX4KqaUwvU2siCZd9S1fHlcnl6fl6WRXWbzDtuajK7KhVNZv+0XsihLdrR5e3sD4841YddVoz3FnzUej8Bc840pFzKPM9lyVDbEmR6pmVpQaYYJhHqmxd1RanyS2OJip/Wl7iAUI9PY5YAmlCko5c6+EFJdbQscT1VOVo0JogqVEBIGJizDNipBXCJRpa3/4nreyM1uL5OS7Zd/4S6aEIoBC6gVHdLlrrMRzK93iamZFedbv2vAZZMLiADuEzv356e5mmuMRQjpSQDfgcj69Gc93AEYTNGW3dBIlcppTPgd5m1PucXu37XElJFXpuBZdR3snJe5mVqWwTF57RNEhVVdVuFsjeo7hxq4dL1v6jbRRJRAuuO0uZfZKAtXQAMqmcUNX6iIqAbBI6luqE2NQUmQiqlyBuK9cWmusyBBpHq/aRq3XQAgEmnuGpUBwJSfcmktM2/VeABlBKj1LEdqWcXVhxAxKWAKS/55ellulxqq5rRUrpaZKfbc1h83QmeNMs+TvYWy2L+HUhG6U1pTEAuhRm5yF4i8WWaue6iFhZCDUXQDNKi50ZSNYoSKhKnUcOdto4qYbLETdW3KVlJgsTVPIkYXArxtTpZQ5T1OdSZmzYpCga1l1GQiNseE4BSav6X5DUShQIjM2TnJYFUP2ihF4HABVy4ntbVXhGvEb7styrMQGF+ealDMzWQgO44HjTkcPZyLNU19HctGynj6k81aQxoXHGWFW1BMXEI8QjAtMwMLkvJuVQAtelfM7NWRVKukc7fmiJzJ7peRRJd61KHzDC18Z1pTs1YqYpXqJS4Vd4waY8YbbtHFSG1TUgVA3L4HzGTvF0kcUw9QIJZluwSUBrs2hu3JHu9B0BgwdcpheoUC1OSoEwmHogokQQ9DOa3t7eXlxdLJ0TEgv1hiDay3GPt5W4iROwT3f5mUWiL7UxYaeYYG6kcDkCavptL6s05FypLLqVkkvmXVsD1laEkwUeln3a1tZG64xHQIb2uUlLbr9Y8mDg1olRkyYLbGVB1QqZZrsZtde1cGIQBYJAwSCYES8XWWCpWJHZG83PSH+rrSqizjoUwyKE5SEQF9cwACQTrfCcqXdWVu1QRXyejMmGQWLvkBcA0XZ6fnnLOcAEoY0hpHP0RjBYfWJOCs9qO6WEQ02Up7o7UYq02gQuSYvqOK5WfGKXo59IArpzUxmAa61Qrt2i6PVw1sm3apBptC0WwFlpFTS1ubqM26Nq5jPXqYoQQiYYqGjMxUQEopUFcVt1yzyoQiderE+WlbizSkB0ycBMcCidR41addWyM2OpH6xDif+UgQhDlnJ+enqdpij6IiE6Hw9gG/NYQmnKHL1wum3cno9EAOlMFCBG05T2tQNPsoK2VJ/t6uOTMpZ4LmyhxjXoa52uIUyszslGLetdRPECkr1dT/QONqeSc0NQiKo26xORSUQNPDXSvcRmAOumIik6NZwiyha2eP5MIdaSdrppgiKdj5uvkaJ0EMPW0UYLoh3VmCAwgDamCnhjA88vL+XyJRiWiBNzd3tmH1gF1TazJFCsxbFKKcQTB6wv6ZsiWC+sWDRP9WAzZFpqfWPFRZLhdn1BbPpJxdV3iZJVbXKSUIKYhtChIn4qN2ykeXFpciuqziBRTqDuTKiNIkIJSYBZMqmxAi9drhFx/F8EBoqG6IQaYUCrEJFq7llZboz63ToqyrMYVGdNz3X15HY4SuB6DKxQL8Nvb2+vLK7d3Ta29iOgwjHd3d2o4e+NsGqzT8XqONRyq7K96XZ2aA69DSeQ6W7STYC19TZYEFmkAEYMLM7TS6pFWupG/+MofjWQaLKRW4na8Q9vMIW6DdTCoQzD5OVV8peo/BGilDqqoDvdRZ56U22q4ZtePtakppUQD2iASsi5Sd5WUOviSzbcSgbVO2ohKm0XgUqRV9U8QMM/z05PMNF5fWb52debT6XRzc2Ota7/ApLCz+Ivo2YdURJgtf3VwrDW/xZYmkCd2c75Nj3C50Cwv2bbw6jxYuiG1La4sAQQ3DLZ0V4eTdBlHgSgjcFkdNaxpfOV1jkWI56q+6/t6JIFuqrSXgDpprbyYqm+TY2VaeFRDZq6jM4m4mYkGLgu3swVZ9lSTzCC03UnETQNVgCENlIi5MCPn8vT0fHl/9+gR6xS+u7kFcDwerSmtk5HL2cKmcbmwxpA+tzvSLDrJvmDUxZP7zpCDtkVMBBYMtljInEuW88KJChdhlDYzBJm1d86c1hvUdXRnth2ZqsUDXf1e9TnCWw0kVA3PTNA5QdRlXubqqZrEYvzMmSEzh8S6BmM/k9hcPalzU54bBiQiLnUwyAxQ4dxGag1dRDCZuWqjvL29vr68WYte6Z/55ngcx5GIDoeDBZnVv9uQuMU9zot1mcLaXdNTe2mQYin2Yay768scIdksREQ0MKMsucGTA/8AACAASURBVDCP4wDZLMNtaRzV1KIpHcXpovyKitmiSKxHdU8PqrNhZm4jdkgd+rYaF0j6urkM141IV6ypRKA6VqvjQpUnNcxLHA4ph4VeQChMRGlglgheVobFAxeqM4ttpEiJubaKUmLUocj5/f3p6TmXxTZWbhLROAzqy06nE68vtYJdsrXm6xKBfdilBnspz613iq1RbGHhnujXzWGYk9dxupOGSJYnS55nAMfTkcGllPq1XnMoBWuxyha6W1k9T/MxtYNes7TjIKi6metbSswtwGmTBSwRVQt9rw1s3kW3oNVdItX3UFv8qFIwN7kJSTcKJKg5teimYPnHjERl6lFGcc2H5Pz6/DZNU31NpsqofQn3bXSWzKSRNYfc20NgIqvpn12r2QRiLG7LvbbY65c0u9CzNKNrxbXlIc5ysqr71qYScSkFjGWac843t7eH4xFALqWUcj2wgVq/aRKpFdjqsXmRZsLrf9mQUTMkkwbhym2yb5aak5J01aGl5gbZxHB1OMXVDa6WZ9DGWtw8XJsEAoNkmArZsZKSgKaG01y3NVFKaUhciqzuvb68vr6+rkiiaQfM97e3GmUOw6D3+sEFZxQbk1jjWkdkQaZvlelzS2a8Dmz6S7NKNtZz2f0oO0QVLpsy1R3uzNM0nd/eiOh0Oo6HA1C/KUREYlpKSb2LVsymOlFokhUwAnQqq1RP1Np0dRcsYyBVrsx0owUpaLFyi3VbMMNgHlpTGhfgOu/drAAQkOpqvc4L6cKb7NQuKIImnRpFXY1FtTSYy2W6PD+/+NOkmqs/HQ4SDMk1jqO1uoLDuYjWS72lOEQvFN6a3yEaZh5tOqyRq08i+txD95NNoOXLa4icUs5ZvHVe8uvrW/1oy+kIRuZScm6alYCnjaGoBsbXoLs6KYmKC0BcmJIc1dm2YDd/CpnTKyhUkmwd1tmplrIGQmrjuouM6xRk/XxEdc+QHa2smBEagDImX2ccisbxsk2EODFn0gOTqW2sAqT5y7I8P73KhLWzJRFxzrf3j/b58XiMOIj2toa2Vo5Zuj7OIgQGqdBD+yic79/NaT2urdjWqkX1VAACyYtpA4E5AZwXfpveKGEchnEch8MhUbOFStJeB9AxgQRAAOrevzbzyzoyg25RqV2+3someklZIy2gBTb1b43UWxtaERWz18Zxi6drLro+rqOtIjxRcmn7HxM4y+vU8iKa7GWqGGNi5vP5bN3Z1WAEzvnh/t5h5Hg8OitEZ6IWiXzj7G5tZ4ERgajXqMhw9VmQOie67b9gk7nGENoIJ6eUqJSF6xiGAS6Fp7y8Xy5y0GxKaRjHQb6G115BrMSPVeFy9gy4HhWrCJZ3ZLmGrGjm1LM626seZOQEFZlcF+tqQ1pkrpMJgtok5xeLQ23pBM5U11blVV1CKSkRX+M4qq/+knwkCen6tQjM8/Ty/Krrr1bhnMvN6TQOfglWJ41sEMPM3a/jWe05M9lCrDWt6SPCVk5tK6fm6dbdvRxp1StRSkPb9EiURshKrZw6XUf7BYxS8rIsuFwqfRGIBvkM3jCMaUjDOAwpMctMAaOdUc8l1zf+S7MXUP0R1XOMYEUyEGiSJ3E7AA+g0sbxTdHpyk9CeXXjb42+Eur2kOaIwVcvWYiSBIDQ36TYMTGYMzNzzsvT8/Nlki1pzOaAKPGbt6ebqHAZ9m8ZSGlih1QcU2iCSEUxC+TTM+7nGAM5mSJRxeweQwCYD8O45KyDdjlhsWSxkpiD6kc2SotaWM5SlHNEZ9RINKVBQDUchkMa6hh/GMY6GK0hEzUiTKDEpchCLUG2vwr31JU0jZkByNJVBUCiBizU3SaAGkX2mBWuTdUzsGVsVkHW9n0LMelLd3WGnInq4d7EKOf389vrW10bXAfLKOXh/gHhYubD4eB5q0HQFhKN4iDVDY+srZ2r0T9Xkw1d7ooo2a+pK5A49pvTzbQsXBbUVVLOQEpj4cw5ywktbdcH1XMagBpptEIZhbmUuczLpE/FCR5G+QpeAkYwyz76UkqdQyZicPsgRG1M2+jT3FXDXQvrZfGv3TO3sz/ENqjoh3YtCfxlO1VqzkswKp8NqCoBc9uBKW6yMMrlcnl6+rYssxlUtDEp850Z4dsrbe80ckGqDVf033iOWbTv1kP9gKn/ntoWd7knkbG+y08SFt/e3Lydz6V+pAHtGOk0DEMhlCzvNrbVn2q8Gqug3bKitE0fLIWXXKbLhZDSQCmN4ziOh/qBYdSuU4QamJHSULikNvejK+dgRhVKFlW1VwHXPXGgdvge2hwitf0ftZake+iYkJjqp9fafCZjSAANiRiJy8JEZeHnl5dpmmox635/HIb6vaxwyVdJJZlOGlmLaEqLGEFYHNXbcMrx0BYYWD72EEFqAeSQEUFj6SemdH8eh7EcT+f3cw1ACGD5TksiYhpIIk6Z3wZIRtvcTiIXN6PH0NQFhes6ueyr5pwv83RhokQ0CKIOh2EYqH3SoZTMuq2f5D1XlgEYVX2B0nVtrjCrTWX+p/IMQc7ZL0RtUDgw18kBgZQ4Z9QtDjX2N190ywBKyd+enl6eXktej0uaO7u7vevECQCAw+FgbRfZwgFFvWT0gw49FkNdN6UpR+eDHIawAcCI0MhDsUy5v7u5KZzPlwsooZTqEtpQGtVByFtX8k5hYS7NpCTv/9S5Gmbj+KDYamEtF+Yyz/M8vb8TpTQOw+FwHIdxSEMuZSlloAT5CnY7RZvakgfXtbdWbSWJyoDVEBovtzBbmKuu/lew2RgeddO/9jrGkudvX5++fftm1wmu2ivl8e5+C0PMrPOQXdtZ5UcodL1H/GBwNL1lkCsb2Zoc1+3zU9e12eoVatQC3pzz493DkIbPX7+cbm9LKQIf+ThDSQm5cFtGlY6bkpYj4XANwGtVgM4gEhFQv+0IjWNlnM95ynmeZwISDeNhHI/HNKREqXAuhfWLkInaC18su8ukKLRJabTjZ2qDtWpxee1dytLe2pRxHZc2Ry/pJes0Td++fn56es5LsdbVm9PhqC+gcQvq7CWTRluWdj9p4bw+JDgmi/+q6WP5o+MMa/WuO7ShmXKjShbltqtgWlfO+fZ08z/+9V///o9/gNLxdJS1NlAiLkig3A4M0XiCroY0a2NX+3Hd8FraMS8iip7CJtXXHd6Zcn7Pl2kiQnV642Ech2Wep3kZx/pSrHzaM2usVvRtIAnDmYA6QYUiURKhfiepTqhfqYiG9tJrkU1Jhd9eX75+/Xp+O+ecVYcWQwTcnE5XBQa4wMw9xhgjQqdLAXp1vx5pk8XICZaNHJdarFghumi7ImYtVq3y2pdXzZCS/9e//c+nl5dffvllOAy3tzc5IzcvRToyA0D1pVNwQ0adDah1pTZpKEeUcV2u0CxtmxqIqH41XQiiMM3TNE/zO9EwDOPxeDweUkrLkglYyjKkkahS35AGOZlP4us6Q1Tk5GJhJfmnTmyjHhQAtJfzwcjIJef39+n15en5+XWaLqwodVYs5eHh0YFGL7WUm3vsxhgRMdE3YY02m9FVXTdlGN83OkZRQMif3YkHizmLHie6q1tY1E7OApgu0+3x9Jd//z9Pz0///cs/SuHb+7shDUMSJ9MkqREuEZUWbieiOruCpIeXE7X1slpJdTNEKhjVoLceli1hEDGY88LLslwIaRgOh8NhPByPB4DykoXdKBHxAHCqYVHRz6e1b1EQs+xvkYrknEYZJnDOpeTl/X16e3t5e32b5kvOVX9ElPNq0FRKfrx7sAfHoncRkR3t0+4AHr3+j4A8yxqxuhiuQBdDtjiwK4H9cydx5M+YRdJc3t9vTzf/99//Mi/L5y+ff//8eVqm0+nm/uF+oIHr9rarh+O2vCUPqPozroG2xEpoH3iEvkVZA6v1dm6JtNp7kgxmzsuyLPM70jCkw/FwGI5pHND2a2rIrN8fASBbt2V2itSNMhgopZQ8L3N5fz+f398vl/d5Xkqpkx4yHnCrFsx8OhwP4+qA1y4gUkq675HMRJF1STq7E6nF8p8gWqcMUvsChTWiC6P14bi1IutEic2IyHNU1CXVLpkRUSl8uVyY+Q8/ffrTn/5UyvLl69Nvv/32dj4PiQ6nm9vbmzpxwlx3EtUunuvArk7rJI2huHnF6z5aoqKvnHALqOrIXFgDLZ5hBudlmZdMdEmJhjQMw0iJBtkJWZfyCFTqAcXMQClMzJlzKSXnZZnnZZrn6TJN87QsuRQ5RaQwUz3vlotTr4REuiVth4cERtcAfFvh9snWucKOqNRGjiZsHKy/jggocfhwvNJtzxZFuYc7PIk2EZznRV7yf7i9/fC//z0NaZ6Xp6enz18//+PpH4nG+/u7h8f7YUjyCQYJdcGcuaQkK50MkvOsW2Amro1Q5xbM8m57FQStZ8lyRZYfobsFcpmWDL7Ix0erYySi9pq/zFeVwoULl7zMy7LkkpdFhoD1NAkpq610MOrRjeaDm0SEUh7vH/Z1rtcwDC6yLrLbvTcwciaOXkh/teMnm97VrtgYXQqbzfm7LhR2fFzEn8OWrQXtC2c6uMm55DwJFh7v7z/99NM4jpdp+vrt66+//vr69jqO4+3d7d3t/Xg4cKHCi8xY1kMVJKiVby+kegY/1x1kEg7LFLYc8SYEKVPiuaasrxo2T8nMXBiFM5jrsY+lFNR3gjKDOSPnUv0ty5lo9S/hytLItL1/rfZuU4Lg25sbd27a1sXMdouI2kv5ZotpumhQq7n4FQFwVrDKRl1BHQ06KbvsEiV2CexlC9E2tzmYte64Fi7jKYB/evzw86dPKaVpmr89ffvlt9+enp6I6OPHD3f3d4fxwKibhgky68M1ZCECIw2iDz0WRIMSeXOXmQtT4UIFJdVZ0brUzyyvBzGzHnVEaiDm6iNZ1v3qVxzADVWZM+d2AETVlbApqG0gH9Nwczw5K2z5NQCyKBv79j4EowUje7kDJCJ6LGT9vFH0aJonbkZzfvdH5NZ7jfvkYfewgViMpCxTTfzx8cPPn/6QhmGaLl++fv31t19e396J6Pb25vHx8Xg6CZ4KZwljwTIR0A4bam+7iRz1xSEmOX2BJYxBkXeLq60YNTRGfQWIwdzIjGv6mprlc39ccpF3zkCJEqf6h6zKXUmZCJCXzrCOULcVUieNJE25bu6jUspgXumPIWlkl66vQM+xxJvR/h09Ggyw3PPYQiurk2Ynl8uONdJt7bHPAZjnGfMM4NPHn/748x+HlKZ5/vLt62+//vqP//5vBj0+Pt4/3B0OB2ZwydXmJTPApYaogMROumENbeFLwnUJ5kWjmRlZHB/La0B1z4HQnlwy9V24bi8ikv+X5rOpLaRo61JZlo+Pj2l7sN29dNJIo121utK8hZc1UAxXrLEcGGwa63kohtgudQysHAydTN3G72jENju2P0qMXRQy8zLPC8DMnz7+9C8//zGldJkuv3/58tvvvz0/v6REt7d3P/300/F0LCWXhQuzvEcmx8dSXfBqJ3rIXHSNjyglFAbJ0RKFipynn0Bc5Gw/PU+QILsZkZA4MfPAKJy4FDmjjdsSTWprcGVZ5o8Pj/LlOPTC3i3F6k4jlyVSDodDp+0souUbC6DIQxaRetPZpxLbEPG0xQ3uiQNK9JgOrBEikfC691ZlRMSlTNMkf/7806d/+fmPaRzmef729es/fvnl5e0FoIeHx8fHh8FoBEQoda6SoUu+1dilGbjuBEnE9S0kAg2cmCmX0jYYtVP9mWtEr56Ti6y5yUcfMU3TIaVPHz6mtonkBy9RnfnAQ4dLFB/OZBYx19g0zNFoyWziEAepGhthl0gigFyCreyxGa4c2/jod1X6ncggUqscM+VWL5l5nhdaFmb++OHjz3/4A1Ga5unbt2+fP3/+76e/UxoeHh8fP3w4Hg+F6vv218mCFmG3DbdEROBcl9ca2JjrJqWaRWe0xSvWZd8CYlnWTSlNl2mepg+Pj6fDEbguptjady5mltG+Cz9iCGuVrLaPxxe7GUQtx+nZ3iuf9fdiW9u4GAWtcxFR23TTRzoCl7iGaWIRLkbZEX9dRLaLuB7Ot9UmMJdJw/MPH3/+w8+H42Ge5y9fvv7y269Pz8/jYXh4eLy9vT0dT5DghgEi5iIH4jLpMTSiTYATpUxt1y5YDm5ri4CiMK7HviVKpZTL+7TM5/vb2z/88U+r/o229Pe9Sz2anSKyClQrSILuS2fq6WzXVQ07Z0frvZQ2cSmlP/3o5shVMpk5JmuZ9qqwVYHzUxEfWyRkG9l11ZrYfXK09ftr0BqvBtyrlpdlWZYFwIfHx0+fPhHRsszfnp8/f/786y+/Anh4vH98+DCMiQkloy6+0fW/SY4oll1PCZCWcnV6pdQwKA1g0Pv7++UyD5Tu7+9vTzfXLZci3rr5W62wlx2LsVn0cKq2arQ67Ka0s5fa1a1g8nU8Z53+Jlq7Ios1LJjXux/aOIbQZk3Mey0Wzq6L2MKd87L+2OHAFYg1vGJR7ieYEY0D69xiqQ/3D3/4+NN4GJd5+fL09fffPr++vaaBDofT6XQaD4chDQCSbFEqiVKhkgZCKcRJ5htlKS2jYJrmaZ7yvAzDcH939/PHnxORnInuBFNbRuG3Lp006gLCdVTBh7WjJt7SJ9YsEA2BZsp+bLS/0AaAZF+p85e6gNrzcQ6LCASjf+6f2rwSozcr4UTFNrxsb7O1zPM8zzMRfbh//PThp5TSkpe3t/O356fn5+dpmuZlyaWMQ91KzuBSMjNx4ZwXibrHIR2Pp/u7uz8cfhrHxEx5WUopi3xM17To//ty+x5jaU5jETpROc4RKQNpXpnjtvrn7u7HreaxuaFGwrUgAPCgjvfWrl29dElIiRQ9DLnCHXwjdrEGn63OwU4unX04Hg7/8vMf//VP/0IELpxLzrnMiyBqkS4kXzSrr4eXsuQlLznnXDLrEuyP+6ytS7M7NopNi8rpdkL7r/NZcmkorS7PFsU64I9QdRCmFnpcX6oxARMxtzN4fWBkjeeeKM3uYXdjMteRdlc7WJNctJ/ThXu+TpCYS86Zl0V3TAtW2ttmzMxlEcwVPUsj0kBXkv+/S3YaxQ6z4yhtk2OI44gfrQ/LjWzRlKbZQwFTSn6/EdYgjeW2l/darS2upi6R4CqNa0BsofNxUWsRMQ7oO7miii1fbiXWkF2XL6yKHMmhxu+hkGDIHRtH+bcuIhqGwZG0M9yKAtbN1xeMrE93ywb2JSSLG5eR5exHZxgnkBZqG3BtDQN8dXBRHZEMupPU/gQWI7H2fEs/rkzXmWBCaVsaeqiKlHm9IStSxZRj6HCtAnmX0tqsK3OvwHXpRlRrWvTAqs+VQbNuuDS5NAEMylXhqqJuRqku6RSWbYxrIZuYPDbYqqarCAcR+0Eujd26L7U4puz2Y8dhO5ZwP7lu4xzrFrHZ524o0KUcx3muDzhU7dfuLt1pZGuMBnZt3+kzTv7SjlSr+yUChqzYo3uXCGtVWhO6UwG45/WtaZmv3dcWyIZXrLqtGGx8n80VgRJV79q5z5HdoiJ8neKiSdC8wBYUuhB3SosG3ilhHMeY11kkujmbppvFdionTOQLzTvGctHrH7G4KGjUNa0LoXVU5PaKqCSxL3aBi2Aq98Q1KoqHYDzXFluOQ0m8Igpd4q3NMLFR3/VuzHw4HCwFWATowDb0au9qYhftMpNDYZQ5UegEtgJ3OZU5YFqOsWns8o3cW5juWG7r2iIk/dXxkMvYZRoH926uH5EN34MyQofc+rUrvF72hZDSjiyG8UfOXiqJHWmqvdwg3/4U+QlrAKB+daqn7m4LY5MsVLlFORawbNaQI9NQi69tX/mRvtiV1pHoVlGRnPfLd022DXdh35au3fupVg8/3mFcl9MtIq4PxCfWQM5Y0Qs7eMVfNVqywqcuILCGW7dLoYc2h5VIb1Y4m8xxXrTxPqytCtyTrnJj+h+/LP93Gd5V3e08jpC2mraDNl1z1Z9cLBzjYidP/Df+abM4s+qHA1n2YnPotYohe9NtsEsZf9rRkUUhtciDQkjRLWFH5u6vWBvsB2kvlqDqcnGS7T+08c7hVtO+e3U7sDlw5zqYcCbrytAtX+V3kwhYm3jFakztAESWr2GuzsK1MjmrbNnyB7Xj4Mzr6Yqdxnf1iKBEl2WLbKIq/1n54/Pv9hmsgf5PXc61oUUObrLHXZrYPdeRfPxJy+ySgicLridnMHNyklm5HbAQOrQtl03njjcwmHOuKtYSNegaoP+qDLHr6GUltMJ0bR8REEtWwba6kHPl7rk1Uvz1By/dIuKawyFcsy2ldRBtU3Zhp8lsya5e+XX1npq75zXZWCFEiW5C3VrXtqEb7rgNQwizeTDewabkbZe0D0SVLaK2+zCWaeWJQEQwANb9JOoQPRT+yCVnhjqF26opbDbE2jSaxW2NdbqKCokLt3AjNdfzXLOxNokq1A7NEEwSq+T13KhdMY7Ndn86wXYoJFraAdr1V1d+V4wInShzV/XOzF2ZsdENYi4ienh4YOacs3oo1wo3b+RYmU040Q1JNbuayYntdOi/fG21EJ84ZX2X2Lc0orNHbn+ckxJrUG45C1u4g1qXayOAbOERTI6QuvdaTheLjoldLifMd6+7uzuduY0zkDDKtDjgdTzksjiWjV7I6jbqbfUFI3sMioUw1sbbV7R7bm1pxYJxz/Gy7bTZtUkO3JESbHarCGwY1fXIHzGwVe4WuGNp2O6Z+xnlfhzHx8dHO3kjq61x/rDbWziEHAiLnhZz3ZlMhD6ZtFW24n1r7ZsQBijoIdfKgTXkox6jop20HAjZNdiaWQWIGLJ63GHZ/f7jAGqF3Cnku0221+PjI61DNDRjdx0c1nDnEIPbnqx591nD9km5rsdk6bDfih6XgbYAh22LWuNZHo4Yj/dY9xJX7I8byUm4Y0hau9F9wWxRXdAoAbiHbkV8Sx73/Pb21p2thhaelvZqrGuL1bamiWFyFM+9MWJ/RbD1asYoFqqSORG7H8WKO0m62nFuTmWNCdDrjt2WYw2Ubsookm1mTBD7ZaQZGLR1rdgVmIOb2IG1XsMw3N/fx/a6KmwkpMtQNmU3QnLCOHbo1mi5qoa69juSHIaFO4Zxjib6uy6eLMy7xttqoS1ky6F0Wxuz2xK6m+YcgCIIbF073SZKGJ93e4tr+8PDg/3knopNRO4omS4vWFEtAuzVzW71aRFmO9J1vZ3M5YpwP+m9LchWFrnEFmtFpLUHcbm6ut6CRRQmVr2lpi5tRLU6SDk8xXLc5ezRlXwr483NjbozOw2N9qoGG/qxhdsRuwi5M08dtaQ1uuGeI2b/Pprqwu3o0Aw2v1WihRobWurisqtNBANHG2zpmgMfuHa6Yh1lutL2CaNrgy7Kt9rixIttdNc4jnd3d7EhEd9YG8vt+3PFRljbAl1XiWJbYa5vhtDandmcETH6a9f7dnETf6J1tKso3JoFiFcUWOW0CSzILL4dprFNhNqFeO3xnd7ZTB87nUTJf7CNRHR3d9dV6dbDqHOHJxvaWgaxpWkT3CqFlmlrv74Z0u099teulG4PtUtjjSc/lfBat/sztiraz7UzSmWfu+Y4UbvJ3OU6fezcW8qJzLSDnq2fjsej3S+riZ1R4tKHKl9Vaq1Z1m8XqT7lXod++/XqwxEbB45ofleiU5lrDAI4nHJdGvqB8X8XdrZ2y0DRGNb2scyd6vYvpR/bRtejXHdC6B5dAfQJEaWUbm5uuv0z2rJbkaMNFbL7gobaorsZMnZjzX6dtnbzRrxeLLN5XF/f0rur2D7RG63ChmKueYoVm8BiyJkTwSpRBV1RowDu2iLsfc6zPX6Hjbqy3d7eooXPtoQdDLkEvL6chLEQl9GmZ7MSZ0FWSlkdcpNScuusCD6lCx33k2o2UgWZrdkOFmxGH1FZERnu3mrKCY8NBHRV1mWLrauLWst/3futcrBW1PF4tIc3WFEtntw91tpzEroE1hVYfLjCo/bc9OnYbZ5tjC00vsvBa1bo2IzQDpjuh0TiNLu1w4SHXYPZAp06YoJoKpXf9QrXxlhFXh/nsANBh6RY2tYTIuqeMiuJ5X1ZbmsP8QSY7yohhj60fSBR7AYOdis2csROG5drrT50GeuN/IOVCa0BqPnTqGVLWqpQJ4N76KwVBe5mcR0pttFlt3m7DVf9ugTdnh3FI6LT6eTKt6Vp13Js1C3N3rv01MLtrUK2eq/rMO2z9us3eS041MbOTg5/sQErnQL1my/rGSZqPi5GfPS9xdouRFw7uQ1JHLacLrqa0mQ23tySyqmV1vHpfkNiveM42glrl91RYxfELiWtGcXhEus34mEQttVqJ4MPbMnE2lqfxr+0funM5rUxskUhFIggV5decqqwImme56enJw6eJUIZ647uGCLC0Zk2NqTb7Wy36a6AIvRap+X4cOsJGozc5JkrWf6t09a8umKx2pewBlDUDK1jGKsre+M0wLIXG4YAXMujLixQXEYBmY2fJBmv57u2eo+myTn/x3/8x3/+53++vr7KTL9tj23SVi933chqxymoix5bS1TFDrXsMGgXWF09y+FXZGKReOOE37q21BIv+7rSlniWnl05q/ONIlQj7mxK2xhah2DXakgOawEMvVHYKSFZxnGUQ3T+8pe/PDw8vL+/v7y8lFKGYTidTqJfN8+mFe2YsPvrFrb0uVOize70uGO57hWTqXhKybpVg4I/tX2DavC58lbOmpHCbXb7XGrMOcuNG1F1cSnVrc5ZctLY1LZWd6yO7foAKCXo2fTMYKYqTSKj8S2rp5Tu7+/v7+8LWBYjL5fL29vb77//nnM+nU43Nzc364+zbBW1BRF96FrtVMOGxmwC21ltw3ckiSLFyzmdqPZYVPxoItbWjLJFHxKlctxhdbLVqLGrlG531NIjDiwVEQD5LP2a1Zw0TjJrztovQaVN455uTh8+fLhcLu/v7+fzeZqm0ilhmAAAG/RJREFUT58+/aC6ozpsi7DGjft1izZkkiKu/W311/0y9RIqKu3QaiuYHYK4P3cusZSbC4zmsJaV+7I+CC+msS2COjU9g0JL3zoslszwzcIIa3NS+OKHJbAu2+mAqJYmUokwlGikYRhub29540tNP6JW1/itErQjOSERApQfqWifKfVPVZddm7KH8MNoz4XMW8UimAbBmjaL4lhfybWM1bWd/JRoDUO5usNvJ6vFcldlco4TtpHnkK4zaYp8IkpEcoCwzSXK3en637VxRIMtsAuULQ10JdmvCwHHzLwsy+VycVGXbt3ndmn2HVg7US1nO8p3zFTMy0ZOG7R+Z9DjDwHRO1fX8PqTu5TVNJmbFLD/rnSUEvRXku9TyUf0ruPBcRyJ6HK5nM/nZVlcE/Zb5KDvcqkkXWO4a9940RjdjMqvAOZ5jpOBVu2axd5jbVeX1+HASWtzOft2W63PLQGNrlyLd82zxTf6RH2ismIUVIvitX90RV3/lMECJTm5IiHx+szkeZ5fXl7+67/+a1mWh4eHn3766ePHjzc3N2n9NQwOocAOjW0l2HqItRlcepvL0YC97GnrzDzP8+FwsCqSJmuwwhuexQlm80Z5NCVtvF+LDaghGK7Ixx6iOWnD+zoMwoR7tHbbDjquhZBjTepnoK5fO0S337dibXvmeb5cLl+/fp2miZmfnp5eXl7+/ve/39zcPD4+Pj4+3t/fH49HK+0ORbke5prs/rV/RhfgjKG/ahZnS3nzFY2qJWXOWU/BUm7QMt16A3pW73ZX3o7NXQivbGT3k6k8TkUkS7OuOJXJKjcCKF7OU6juXKes7UH97muiVNgMeQgpJfm+ne1D9mzveZ7P57MEDbS+5nn+9u3b6+vr4XC4vb29v7+XCSdxgk54K9UOyLoQ0bZYTxH5xhq4y0MyT6YC6MyNRpYWf2oU1xYHICekhQVMn3FprB4shuyvtl32z75T6/KH/ZN6dI013smccrIqRO7LtaeuANqMYgePyRwdX0o5n8/SiT98+PD09CQ/De2SeyKapomIJHgax1HmnG5vb908u1ONU1xEAK8vp4q4qV71gPU1z7OL6uy9wovNwHnr6gLFGc7ayMJFq94xtAW6/VVrGe3TWOVWMxw4trLbUOnahpYHhETy0Wmj4gTZWOIMqftJBBZS5vF4/Ld/+7fff//9crloq4ZhEPrRCX4A8zxP0/T09CQ/3dzcnE6n4/EYR7YweIoGQCCV+NBhkY1TRguop2lyVGrVqH7N9jFngi4CumhzHKNPbDgLeIVjDTjnT2jtNFexkdWmhYLVlFZmp6dcP9ZCVFOOdQBQIub6UWiXS77nos/ZzKlcLhcdy0iCm5ubP//5zzpkk0HcOI72MDKnevmyzLdv30SVx+Px48ePwlLOPBFJ7rn7NSrKKlYacrlchITiuNUmjnObsdNak0d2ceGOk9/ZWrM7RoiFd1HViRjsz05fiuKoStdCJ9wK12BqHxixeSrtgZhQ1pYQKMvMimwZU/Hk/nQ6nU4ndTSlHbzEZnd6aQcn2DHBNE1//etf397ePnz48Oc///nTp093d3d2oOSAuNXq6OCwBpwIP88z1gfMd5EUgwFnJouALQayJrP3Wo7dAsRmqGRL3nKO7qEPsV37rTaVlmzkZTUV6U6vVeTORERsIomVMep36b3oOefz+RxZDQbcrihqJ0RbHDuqOBwODw8PpRTZmvL8/Hw8HiU8f3h4uL291cPwNeMW5cQ/SymCnvf3d411rLUiAr4LWdvGLSraCqQc5URcWr9m7bjFMnqNzhO5nDsqiz1DG++QpMLZnm1R2GU+V6ZgSHQkJrHluEIUQIqe0jsMSlJ++PCBW+QuERWA9/f3X375RXju9vb27u5O4GUItf/Vypzzsizv7+8SjdmBmF7OohbfUefdfm7lt+iJFOK6UASfJYgumGw5djOdZY2R1nRChoesmV0F0SrWnBGUKq5ziFZHNotjzsvlIoWL5WRXF4fjkbQECyY2oZX9Va+U0uPj4ziO8sk9mW2Sj4fmnF9eXoSotMA0DodhlC8Py6fTJGTW8qntuxK92e2dzur2CZmBLXpHGLpmuhtak0cXrK4EOye04xkdklwr5En/Q1gIvaHLLrGjuCdxaK0razH6dpvMVWiJiC0VOQG6nc91BiuMWwMWG9/e3t7e3pZSdD5Qta+VipMqsvAVToE9nU4S2osf1CXCLbu6G/ttoe4EgbORXtrPt8zhzE+NVyygnQ61XVbDsXxN39msaeu2gkbzdFvo6tY/Xexi6dHm1dbKvXR0pTGd6rUOS+sq68+p2vbLqrVymA26HcWqhPHdiarcUoVW+Usph8NB9gFLdfbT5rqlREaRNhmte6Yt0GnGws7ldfeu39KaeLpmdTxiTWALidjQ9KsQewvO7qaLJJfMSYCwwq8l6KWiW1vKN19dUVgTsutV1nXyemOoToXrUqiznBWewotEV+s2dMh/ZaZqMJdkeXt7k9l2IhIAyY1MhNo+oK1W2Dk9IGDI6UR7pl0It5hwC+RauNrFchIHhtvCmdyvnJqDhXsYAZTW72XbWmF6OQIzxRtrP0WVhKs2cnSXZFGasSCLnkuLFcXJgig3J6LMZIGldrUwki9eq7GHYbi5udEJTzXS09PT+XwWVCmGpBUyCy/H7zmAWkBbPSsU9IbNEN1FNiKGm10k48s0rwOTo7F9k9kO1lmadVfX3hZSNplL4KjIYiWWaZsq2pQ5Hqs41bKddBCCUadJ5sw5G2KzcXzyryzrsnFqqh1eexmtt6wxdDweZVJAXFVqr7i8vb1dLhdBj4WX7pSSZUGZa7B1WR7iNXPrZX2i07+d0nRs5HqyQ0+0hVWC1sXBycjD0WaLXOIMzL3ZIwsO/RcG1A5wCESFwHxqctVIMZtPaO1PbeyslEPGY1LYsCDqOx6Psjiqzsvuq1SKknuNsqWWw+GgyynUhmZqRVlbdRhS/cglU5EqJAwtYU0JDkMWAW7OUDub68CKLVssGwfSNVwEhnuohYwOBDGnPrTYj5erqYtuh/rIZxy+mmVpTNyQC32UbJRmrADFLO52WVCcUc5ZFkphzgeWS/+U9PKOiqBHYiA3LhMt3dzc6MqMJQ82sZoeN6MYUjTzei6HAgM5H+SU77DVhYUVyRUYjeLuHRLIblvDGjqWvrodOkKke9+FvEtc2jKFZlEZpCuz8V9avuN/CzgyM1sKOLRdxuoWpRypN6V0PB4lGlOKUkJS40WH0rWoHI42z/PYLpVZxp7MLPMLtjOw8WUu7tmqncwR9RZhcjmPERGgGW0y2qUl/dVm+c6aGgwbuaDMIsPmck5Kb1yZKpBFp22VWl2J+nA46ASSLU2Rwb3hBpurG61LFYotmdu0BrbWpeCjbZ+29x8+fJC9ddze/JIscqCsTIhb+rE1Um8CUy8bAFlVpPW2T2e7iAxrevtvhJTDkypZ2zuycWQRQ+7elqudxpVAaxq07YlgdaI7s3EbTgtQRGiLJEVbMhNLykPFnDNvi3UQ0Vo0TUyA700rdzuuDOK0BEkmTnCeZ51v5DDdkDYuWpMTerBwrKz3aT2aU+RFw2lplgJUtm5LvVOzjela3dVn6+hSkaIttsHJZ+/1km031F5rF1XKfiP71mVZr7GU9ateFhwqjMWKDuhgEO/GUGk93dLVj6UlhK2kmkCCvBi/K6Q09rIRuuMkSwZauNMz1jjburHd3gKRe2FT145j9wd38TriY+M7IpJsrvhTN7EqIq03dQhiRJUyeJaUwzDo9lNlKeUe3X6qf4okNo2VkA2lyXONlzWjJnNFWTLuUngcddtKHQ+JGKO5HBXJtRWfuarjjZMEAWQIUwAW/Q61tsDOvFHMsEVLLqXFGZsQxMLFlmbj5SicWlcWQ+TSUEkAp4GwWsW+H0jGx0UeUoHtRJRjLxjc608UfIfTqbOQNaf9dBXWvIu2y0/ODNVlkwigtB7qb/FEfO5Ecuawwne51hVuBx+jK2uHWpyCYEAa1R1LsFLKpUZ1tGRtibasRkTi3VSJwh8yXNdRlZ0L0MIdlzhhbI2OCyOG7J+2ZFtm14PbNlphdDAoN7K+azEklxrM0o/tww4N1t4OE1pCtF1XYFemjrRsspHWJnSWdvdW45FILSBcOXqpF3DmVEXYEjSZzOigOSmZsBFyEjAJCZW2Pq+hhno0e6X1zJNlrIgDB0Srlu92WfdEsW4BZAU7tMvBSDqPnn7hdEUhJLJ8w8Gl0PrkbvuvQ+RW66Jxr9OPWHcym9RBzRZkEyczI+DEsl0wkpzqwo62nOgSVssZIxoAcXNV6pIkEtc5Q72x/BThlcwUANYzUl2utX9GdUWLauHq1CyM0Lrl8XiU4Fph5HCjTOz4BqH7yaXDAte97XBhi7rsfewwjv9W29a6mkIgvSgur4mKgr/ABhax7gEWTGQ+iCOF5Jzf3991Lb0baUqIqrAo60UMO7JTaW2Yolrm9YVtJGHdc6J+pLpIRa5A5aHD4RDD6i2sdH9yNt0RzAnvkJTC7CVCvyJdU+sa214uT5TV4d3mTWYO2rbTQl51jRYma9xjnaAkvlwuGmvrth69gSF5AYT6OyUD56esbGW9zKKtcxMK9ldsdzOVXAGtpVkwSZM1JLKdRNsVUdWFVPffCIL9h9Y0tCYzXnsefb56E8oZO2rH6sVBtWx/yA09IDp1q2qSmWhOKR0OB3kHzV5qEtWvosp6RpjOKmASgOrFbXeA4sY2x2JFNetwxoGGae0smFnmJmwc7aIiIhIqsu7MRkUIfc9qbwtP7iaiwf7ZzWhzOfTIpbMkI9bHYtpuGo0dRYeZVVKd6r3VaYR2RJILFUspsgBS1puH1Gz6spGCSRdKo9fTP+3iiYLJPRHYaV06qJTyLTltdRg0uCtu7L0qmZnFkVkM6bu/qbceAjNSU3M4xXaVbB+mMDXqMtrO0C3BCuDZSLuy7Z20zS6OwyKu958jdBTlITH2OI7H4/F8PrtuZ8fkNuix5KT3zhIA7MuQvF4esd6HjTtjMwdrXa3rHq4cWeuNDGQL17DazjfGSSPXzSyGtO0RTN0nGqRHirKGpjU5RSTo89EB5bvAjERlpbFiRekt/Mt6bsbWNaxPQQBQSnl7e5MjRLsCYA0peS1VaUm7tf4bO7e1roDMOj6s4yFNaQeneqMyKIZk10Ax7wtoaTrZKBjamnJUM2HNCrYVK0AnIobNZRVlIwdbrDMc9VyTM4386d9TU7M5kGqJMbFLFh9Gw1ueswaACcmpBZgA7u7uSinPz8+yhdkygS0z3oghuyxFJoAlE5VrXuvmYFClyawYNnE2F5vYyI72ub0IIKMzDa7d6CGFqWrLSREfMNMBlOSdUiRKCvpITtjgDvskPlQvL3+OWFOILSsaaYcA7RV/7Wa3P7k2xF74+PiYUvr8+XNKSU6A1MsW6IZUMGZWjTg82cumsRRV2qEl0TGpDPKT7lWSS1bylZYs5sRf2/lGN2NkUe5MHvmDzbikaRMJBHSG7prddn5niC1UIFARuQONHQ7iky3cdLGiJcPMeltu66aUS2cXbawg+y5+++23z58/H49HYSasKWRfSLWipaguqrT5lh1LeEvJOj6NowU0ugNO2ciRqM4SOTZKYWDv6McByz5JZmbSXap2+6dVoDbQ2tGmUfV2QrSXl5cuyko43hvo04kDR8SEa0yEufvVPtEeLJYQq7y9vf36669fv35NKd3d3clBfWwcTSzflRz7WVrPGpBxqVh7Meue9N5GPxZGcdZRnshWbtmPq0iKDhe9zdSOSywmtprvGqs3dghsC+R19ELhhBMPypeXl651twARCSCt1+qcw9pC3nfbbBGsNrB2en9///r162+//fb6+ppSur+/18NAIu7322KFcdGJlcc6r2LW7+zasIVR3JgmCYjo9vZWYGTZaCu4dlixtGExEdsYe0vUM5nhDgy2otJcp1rhTNjIgcY5NcdJXa8ZPavcp/WUUuQwl2uLrthclgPkTZ2np6dv3749Pz/nnE+n08PDw93dXVpPh/5IZ3WSkwlyFUYW0PZGweQWztgE4JLm5ubm7u5OXw2IW4usACpwWi+lbd1Yp7ND/FEPP8Jk2Oh7lY2irnl9fGTsyhyGuxFMiutImztiwSF9HTs7StDR9bIs0zSdz+fn5+cvX768vb2Jy7NniXaV0sWuSmXp0F5uqY7bocT6pHs/TdPNzc39/f3t7a3GRspDgzlJ3PEQwn5qTdZV1w4nOXO4BM6BxAJdYlgYqYVilRFAO8aIDYh+xAnXJbb4q8OZVqSd3rKC7KV/fX39+vXr09OTHBF8f3//8PCQwopNlxH1iYJAK3J/lvU8uIuESpslyjlfLpfD4fDhwwc5MlDGaNTCIDsuU9yIDDY86sIdPaxgzQU7oLGGc2xiC+/CoJagbOQuC6xuKbR2ov/steW8XBoYAFlFRNRaqlCKki1v4vW+fPny/PxMRHJCrcz7cZi7t1VjvWXb3TvPxWH4pj8ty3I+n0+n08ePH+WoJKndxkMa6zhfpvp3gqkSaL1/CN9zTxy+QNJNHMHHPY9R719fX7s/+HTfmyVyf7JZaIuGdzdbpGoV52C0I4aylHN5SlESSMmeEzlVLQbmWpSjHwcae1M2JgLO5/PlcpED4BVDuvynm9HcxJVr5j6SugZynKo8p/pxauS1p7Oa3zL31SI2Nop5bKE/Qh47vLdVAgVXZUMux7c2S5fPbI90hrezgtM0XS6Xl5eXL1++fPv2jZlPp9Pj46P7xJaWo4jBOixz5OSQdD6fZQ3n06dPcgSgnoFkSQj2SynrrmIph3vujNch7I9oOLqRaB1HRfHyKZ+fn6P73JHpxx/CgNLB0Q0looK60u+n1OdWrXKV9WqGjrCEpeQU26enp69fv76/v9vAHL3geouQLPk9Pz9LJPTx40dBp47t3TQjNtxWt89EJWx17B0E7FzdbhlDlyjMNcRG+DZvt9zYmC16jOn327BfJrVYLa03k+ivW7msGBENxayhqtf79u3b09NTznkYBjn7UVaF0dxc5DmZd3h/f5cXq+/u7j58+PDw8CCTQ+5okbT7akfHZaxb4SIb2/bYb21eR+2xD3d7tXvIZjO71tUPsaM0aX3KgrVcvNcqS/gGjdPRFo0532TZyyXARo/c6kAqhkWDm0gUWMgnSuVrJPqJKnW4AkElNgWcDObtnFAKS8Iq3g5ioh72f9rq3hoVRUe2pX9rza4CO3K+vLw4nLqFhf12OrJx/cYli9l/5IoN2AJT7ExWoa6HWalcTMPrhXo5U1bCKXu+u5Qpk4duhdWxDrXLyrmFoS4lO3s7T+caa8vsdt2uVnc0v++gED9ZrNVsGdK1zT209UVQuyexi0R7I5jcPXdPInylwK7urGByDebgNnVYDmH2slU4rDirR56wQrrFTgu1Lly0hLgS5SLdtLGpKN47u7iMsetqpaSvO5b1C6yuDtcSW6Iyp32yRWZdGyN0hWLea+4afov5nL5K2Icf80a7OsOk9Y62WF1USLfYLXxbg1l1OeZwGnBZbFeB2QyUwgajmFiV3EWV3tiiYuIaG21BHr3L1qGut6smB+GdNFFTW4rm3SFuVJlN4LTgbtickoONL1ypwGJj+2dUlC3flkM9Gt5RVNRnN5d7HruQY8RYXaxC0RxJyJY8WvJ0md19xLUq2tUEw+pdLVuu3irfGTj27AiCLV1oShfyR5spFUW1qI9Qr7eT2FUamdWusxZzDrOtJRKMa2Dkjy6wLNE6CVVyrIGlgim1w7zwbkEpf47cg7lVRPypmJ2ECD1+y4QUvIyrxZUQleUIIKZEr2NF3oqJrTCqcSebhexWh07r9305vM+16RSMJm2WWJfe2Aaqafe9mGvvYM5aQei3CGt5TiqbcfOt2X0bu/Q2QUSD/BlnsZw5I1vGxttaYu9xGREwFGHXbbIr0DKQvbrNUcPbrmx1EvXpyMyaUFW3s+nFgkAFVr1ZKrIQ3+q6FtAucexUevnzjWyXdSXuGwxrA8TErhyrsi5Gt1jNFqjWtfCyD20TIpmhZxUrYeygNrsrygWhrvnWobgZVH3H3ILJqtH6RPvESmKzWGPbjC6mdLqlsMSrvzqqi9pYfSS0C7fYgeQqZv9lxL6jAadTlz6SmXMNViOuBFeLlcoK3xUjyrlFkFH73d7lcODybvXDKIwOtWxnjr2iu9+LTMiy1R+0KDZxmL2skBT2QyJ0p5EDDmLDtnpJvIncYO3R7dZdBrK2j7rAxuVs78rsIjvyU1ekWPVWt7Yyx06FHwCTPoxzQrZYWlOOPEnmOxaREZI5YMTOqig7OutrvbrPH2tTXkvo0k8sKNrG/iQ3duDWrdJVYR9uWc5p1okay7T3XcBZgeUmDuxj7Zb8uv3VPndMibX9Yr+3N3a7iBZu9wJY0Njsiqe08XKIa5otv3vZhisbucUcLdYf2hd7apQDGxaygHDgcMCi4BEo+CwnEq3JzElCa47tAjEyoqbvIilqQC7tx7FMV7JThT0XwAnZxaXjIYuebj90yXjtsHh91EJXOVogG7doa8Q6yLs+JONcbAaVqdtBHVq1RFeBRYkK4aCj91pdN4GVKsoTNaKy2SZs8VNsWuwMWpQmtuCz8/i2qKh9Vazt2TqHEhnLrs057+M6vL00jdZiJwVcIS6jNWUK7xFYNOuT1RDAsbGqbEuzWAPFzWh38efsahWhf+5vqtpCKtYoiaTl2uLg5SRBwE3Eh5VH3Za1KAJ6NLtbsYrOYquTREmw9qf2oculT1xs7mRzZWqWrefDMNDT0xPW5qQ1MXYV7US0NqPgd2wyCh7HPkSwULe0yB9Rzm7GLdmsVN1Bb8SNK402vEk3fVfg7o2lLi3KMaKVLSrNmcY+KeYMZ61uaynTtsJucKjdwJbuUBmhHRHgKsAGxu1leTLqxT53BNmlN82+RUtR4O6lklheib9uEZ61DfXeMd1qlIOOldYSQ1dXbKa8HRdq1V2es0+2nruH9j6Zd4tZQ+wthe7get8d2IZF1cQayfhBqxFVhIPvllRWNd1NB10u3CrZts4aKcrv2mXL3IL+1p/7eZUAbIu6eo5V2OeWTra6Pa2nizQ8T+aDf6W9PnVdXNxqzI6OttLHZLy+tlobt0y4ZI7/XFH2V9V4rCg+tA7ICWDF3pIKa6pwQmLdMy1hxD4TecXFMQjwsu2V55ELowewAbtcbqbAtsWCCev5i1q+04szsxMUG/2Sgge02lfMalMdVUQZnPaxntK17bQadOAGwKHYLe5EMM//K9QMkiiEQRiK3v/OunCGhpcwulJaCoSAv/0CE5f7tDJmu/W2pTdQwU8Wt8Wo73H4hwWd1sidb8cgb9JsS5GAqlNLSanE1SFHbCgUh8YfkQBPQ6Wd1/Fpkl6j2DpfJEQ0h161lR8810UQOKKLjjlWR36FotIW0iq67dc1VV3x9ENRLYnwXUFZOmNB/OQvJanbkk7AX9mxLCKmPept5rTMGoBGfmuAWnMeeI+CEAooMHQiRnyUWxjdHB62nsYhHL0iKPQbuKQRfZe/VVXyzXkBJzQH57yHvnAAAAAASUVORK5CYII=" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "Aeotec Limited", "NodeProductName": "ZWA005 TriSensor", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Notification Sensor", "NodeGeneric": 7, "NodeSpecificString": "Notification Sensor", "NodeSpecific": 1, "NodeManufacturerID": "0x0371", "NodeProductType": "0x0102", "NodeProductID": "0x0005", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 3} +OpenZWave/1/node/37/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/32/,{ "Instance": 1, "CommandClassId": 32, "CommandClass": "COMMAND_CLASS_BASIC", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/32/value/621281297/,{ "Label": "Basic", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BASIC", "Index": 0, "Node": 37, "Genre": "Basic", "Help": "Basic status of the node", "ValueIDKey": 621281297, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/48/,{ "Instance": 1, "CommandClassId": 48, "CommandClass": "COMMAND_CLASS_SENSOR_BINARY", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/48/value/625737744/,{ "Label": "Sensor", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_BINARY", "Index": 0, "Node": 37, "Genre": "User", "Help": "Binary Sensor State", "ValueIDKey": 625737744, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/281475602464786/,{ "Label": "Air Temperature", "Value": 20.700000762939454, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 37, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475602464786, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/844425555886098/,{ "Label": "Illuminance", "Value": 0.0, "Units": "Lux", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 3, "Node": 37, "Genre": "User", "Help": "Luminance Sensor Value", "ValueIDKey": 844425555886098, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/72057594672070676/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 37, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594672070676, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/49/value/72620544625491988/,{ "Label": "Illuminance Units", "Value": { "List": [ { "Value": 1, "Label": "Lux" } ], "Selected": "Lux" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 258, "Node": 37, "Genre": "System", "Help": "Luminance Sensor Available Units", "ValueIDKey": 72620544625491988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/634880017/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 37, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 634880017, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/281475611590678/,{ "Label": "InstallerIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 37, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475611590678, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/94/value/562950588301334/,{ "Label": "UserIcon", "Value": 3079, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 37, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950588301334, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/281475607691286/,{ "Label": "Motion Re-trigger Time", "Value": 30, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the delay time before PIR sensor can be triggered again to reset motion timeout counter. Value = 0 will disable PIR sensor from triggering until motion timeout has finished.", "ValueIDKey": 281475607691286, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/562950584401942/,{ "Label": "Motion clear time", "Value": 240, "Units": "Second", "Min": 1, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 37, "Genre": "Config", "Help": "This configures the clear time when your motion sensor times out and sends a no motion status.", "ValueIDKey": 562950584401942, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/844425561112596/,{ "Label": "Motion Sensitivity", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "1" }, { "Value": 2, "Label": "2" }, { "Value": 3, "Label": "3" }, { "Value": 4, "Label": "4" }, { "Value": 5, "Label": "5" }, { "Value": 6, "Label": "6" }, { "Value": 7, "Label": "7" }, { "Value": 8, "Label": "8" }, { "Value": 9, "Label": "9" }, { "Value": 10, "Label": "10" }, { "Value": 11, "Label": "11" } ], "Selected": "8" }, "Units": "", "Min": 0, "Max": 11, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the sensitivity that motion detect. 0 - PIR sensor disabled. 1 - Lowest sensitivity. 11 - Highest sensitivity.", "ValueIDKey": 844425561112596, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1125900537823252/,{ "Label": "Binary Sensor Report", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 37, "Genre": "Config", "Help": "Enable/disable sensor binary report when motion event is detected or cleared", "ValueIDKey": 1125900537823252, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1407375514533908/,{ "Label": "Disable BASIC_SET to Associated nodes", "Value": { "List": [ { "Value": 0, "Label": "Disabled All Group Basic Set Command" }, { "Value": 1, "Label": "Enabled Group 2" }, { "Value": 2, "Label": "Enabled Group 3 " }, { "Value": 3, "Label": "Enabled Group 2 and Group 3" } ], "Selected": "Enabled Group 2 and Group 3" }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 5, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the enabled or disabled send BASIC_SET command to nodes that associated in group 2 and group 3.", "ValueIDKey": 1407375514533908, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1688850491244564/,{ "Label": "Basic Set Value Settings for Group 2", "Value": { "List": [ { "Value": 0, "Label": "0xFF when motion is triggered and 0x00 when motion is cleared" }, { "Value": 1, "Label": "0x00 when motion is triggered and 0xFF when motion is cleared" }, { "Value": 2, "Label": "0xFF when motion is triggered" }, { "Value": 3, "Label": "0x00 when motion is triggered" }, { "Value": 4, "Label": "0x00 when motion event is cleared" }, { "Value": 5, "Label": "0xFF when motion event is cleared" } ], "Selected": "0xFF when motion is triggered and 0x00 when motion is cleared" }, "Units": "", "Min": 0, "Max": 5, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 37, "Genre": "Config", "Help": "Define Basic Set Value when motion event is triggered and / or cleared", "ValueIDKey": 1688850491244564, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/1970325467955222/,{ "Label": "Temperature Alarm Value", "Value": 239, "Units": "0.1", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 7, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the threshold value that alarm level for temperature. When the current ambient temperature value is larger than this configuration value, device will send a BASIC_SET = 0xFF to nodes associated in group 3. If current temperature value is less than this value, device will send a BASIC_SET = 0x00 to nodes associated in group 3. Value = [Value] x 0.1(Celsius / Fahrenheit) Available Settings: -400 to 850 (40.0 to 85.0 Celsius) or -400 to 1185 (-40.0 to 118.5 Fahrenheit). Default value: 239 (23.9 Celsius) or 750 (75.0 Fahrenheit)", "ValueIDKey": 1970325467955222, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/2814750398087188/,{ "Label": "LED over TriSensor", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Enable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 37, "Genre": "Config", "Help": "Enable or Disable LED over TriSensor This completely disables all LED reaction regardless of Parameter 9 - 13 settings", "ValueIDKey": 2814750398087188, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3096225374797844/,{ "Label": "Motion report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Green" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a motion report.", "ValueIDKey": 3096225374797844, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3377700351508500/,{ "Label": "Temperature report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a temperature report.", "ValueIDKey": 3377700351508500, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3659175328219156/,{ "Label": "Light report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 13, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a light report.", "ValueIDKey": 3659175328219156, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/3940650304929812/,{ "Label": "Battery report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 14, "Node": 37, "Genre": "Config", "Help": "It is possible to change the color of what the LED blinks when your TriSensor sends a battery report.", "ValueIDKey": 3940650304929812, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/4222125281640468/,{ "Label": "Wakeup report LED", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Red" }, { "Value": 2, "Label": "Green" }, { "Value": 3, "Label": "Blue" }, { "Value": 4, "Label": "Yellow" }, { "Value": 5, "Label": "Pink" }, { "Value": 6, "Label": "Cyan" }, { "Value": 7, "Label": "Purple" }, { "Value": 8, "Label": "Orange" } ], "Selected": "Disabled" }, "Units": "", "Min": 0, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 15, "Node": 37, "Genre": "Config", "Help": "This setting changes the color of the LED when your TriSensor sends a wakeup report.", "ValueIDKey": 4222125281640468, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/5629500165193748/,{ "Label": "Temperature Scale Setting", "Value": { "List": [ { "Value": 0, "Label": "Celsius" }, { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Celsius" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 20, "Node": 37, "Genre": "Config", "Help": "Configure temperature sensor scale type, Temperature to report in Celsius or Fahrenheit", "ValueIDKey": 5629500165193748, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/5910975141904406/,{ "Label": "Temperature Threshold reporting", "Value": 20, "Units": "0.1", "Min": 0, "Max": 250, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 21, "Node": 37, "Genre": "Config", "Help": "Change threshold value for change in temperature to induce an automatic report for temperature sensor. Scale is identical setting in Parameter No.20. 0-> Disable Threshold Report for Temperature Sensor. Setting of value 20 can be a change of -2.0 or +2.0 (C or F depending on Parameter No.20) to induce automatic report or setting a value of 2 will be a change of 0.2(C or F). Available Settings: 0 to 250.", "ValueIDKey": 5910975141904406, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6192450118615062/,{ "Label": "Light intensity Threshold Value to Report", "Value": 100, "Units": "Lux", "Min": 0, "Max": 10000, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 22, "Node": 37, "Genre": "Config", "Help": "Change threshold value for change in lux to induce an automatic report for light sensor.", "ValueIDKey": 6192450118615062, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6473925095325718/,{ "Label": "Temperature Sensor Report Interval", "Value": 3600, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 23, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the time interval for temperature sensor report. This value is larger, the battery life is longer. And the temperature value changed is not obvious.", "ValueIDKey": 6473925095325718, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/6755400072036374/,{ "Label": "Light Sensor Report Interval", "Value": 3600, "Units": "Second", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 24, "Node": 37, "Genre": "Config", "Help": "This parameter is configured the time interval for light sensor report. This value is larger, the battery life is longer. And the light intensity changed is not obvious.", "ValueIDKey": 6755400072036374, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/8444249932300310/,{ "Label": "Temperature Offset Value", "Value": 0, "Units": "0.1", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 30, "Node": 37, "Genre": "Config", "Help": "The current measuring temperature value can be add and minus a value by this setting. The scale can be decided by Parameter Number 20. Temperature Offset Value = [Value] * 0.1(Celsius / Fahrenheit) Available Settings: -200 to 200.", "ValueIDKey": 8444249932300310, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/8725724909010966/,{ "Label": "Light Intensity Offset Value", "Value": 0, "Units": "Lux", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 31, "Node": 37, "Genre": "Config", "Help": "The current measuring light intensity value can be add and minus a value by this setting. Available Settings: -1000 to 1000.", "ValueIDKey": 8725724909010966, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/112/value/28147498302046230/,{ "Label": "Light Sensor Calibrated Coefficient", "Value": 1024, "Units": "", "Min": 0, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 100, "Node": 37, "Genre": "Config", "Help": "This configuration defines the calibrated scale for ambient light intensity. Because the method and position that the sensor mounted and the cover of sensor will bring measurement error, user can get more real light intensity by this parameter setting. User should run the steps as blows for calibrating 1) Set this parameter value to default (Assumes the sensor has been added in a Z- Wave Network). 2) Place a digital light meter close to sensor and keep the same direction, monitor the light intensity value (Vm) which report to controller and record it. The same time user should record the value (Vs) of light meter. 3) The scale calibration formula: k = Vm / Vs. 4) The value of k is then multiplied by 1024 and rounded to the nearest whole number. 5) Set the value getting in 5) to this parameter, calibrate finished. For example, Vm = 300, Vs = 2600, then k = 2600 / 300 = 8.6667 k = 8.6667 * 1024 = 8874.7 => 8875. The parameter should be set to 8875.", "ValueIDKey": 28147498302046230, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/value/1970325463777300/,{ "Label": "Home Security", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 8, "Label": "Motion Detected at Unknown Location" } ], "Selected": "Clear", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 7, "Node": 37, "Genre": "User", "Help": "Home Security Alerts", "ValueIDKey": 1970325463777300, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/113/value/72057594664730641/,{ "Label": "Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 37, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594664730641, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/635207699/,{ "Label": "Loaded Config Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 37, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 635207699, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/281475611918355/,{ "Label": "Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 37, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475611918355, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/562950588629011/,{ "Label": "Latest Available Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 37, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950588629011, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/844425565339671/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 37, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425565339671, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/114/value/1125900542050327/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 37, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900542050327, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/635224084/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 37, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 635224084, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/281475611934737/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 37, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475611934737, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/562950588645400/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 37, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950588645400, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/844425565356049/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 37, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425565356049, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1125900542066708/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 37, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900542066708, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1407375518777366/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 37, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375518777366, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1688850495488024/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 37, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850495488024, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/1970325472198680/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 37, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325472198680, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/2251800448909332/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 37, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800448909332, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/115/value/2533275425619990/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 37, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275425619990, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/128/value/627048465/,{ "Label": "Battery Level", "Value": 90, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 37, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 627048465, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/,{ "Instance": 1, "CommandClassId": 132, "CommandClass": "COMMAND_CLASS_WAKE_UP", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/281475612213267/,{ "Label": "Minimum Wake-up Interval", "Value": 1800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 1, "Node": 37, "Genre": "System", "Help": "Minimum Time in seconds the device will wake up", "ValueIDKey": 281475612213267, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/562950588923923/,{ "Label": "Maximum Wake-up Interval", "Value": 64800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 2, "Node": 37, "Genre": "System", "Help": "Maximum Time in seconds the device will wake up", "ValueIDKey": 562950588923923, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/844425565634579/,{ "Label": "Default Wake-up Interval", "Value": 28800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 3, "Node": 37, "Genre": "System", "Help": "The Default Wake-Up Interval the device will wake up", "ValueIDKey": 844425565634579, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/1125900542345235/,{ "Label": "Wake-up Interval Step", "Value": 60, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 4, "Node": 37, "Genre": "System", "Help": "Step Size on Wake-up interval", "ValueIDKey": 1125900542345235, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/132/value/635502611/,{ "Label": "Wake-up Interval", "Value": 28800, "Units": "Seconds", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_WAKE_UP", "Index": 0, "Node": 37, "Genre": "System", "Help": "How often the Device will Wake up to check for pending commands", "ValueIDKey": 635502611, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/635535383/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 37, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 635535383, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/281475612246039/,{ "Label": "Protocol Version", "Value": "4.61", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 37, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475612246039, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/instance/1/commandclass/134/value/562950588956695/,{ "Label": "Application Version", "Value": "2.15", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 37, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950588956695, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0", "1.1" ], "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/2/,{ "Name": "BasicSet report", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/37/association/3/,{ "Name": "Temperature Alarm report", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1579566891} +OpenZWave/1/node/39/,{ "NodeID": 39, "NodeQueryStage": "CacheLoad", "isListening": true, "isFlirs": false, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": false, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0371:0002:0103", "ZWAProductURL": "", "ProductPic": "images/aeotec/zwa002.png", "Description": "✓ Standard form factor and appearance of the light bulb with 800 lm output ✓ RGBW: dimmable from 5% to 100%, tunable from 1800K to 6500K, and 16 million colors ✓ Possible to be included in groups, scenes, or schedules ✓ Suitable for indoor lighting: Corridors, Bedroom, Living Room, etc.", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2881/AA LED Bulb 6 说明书(RGBW-AL001)_转曲-2dd.pdf", "ProductPageURL": "", "InclusionHelp": "Add for inclusion 1. Ensure the led bulb has been excluded outside the network. 2. Triggered by OFF ->ON (between 0.5-2 seconds each time) 3. LED solid yellow Color (0xFFFF00) during the pairing(Timeout is 10 seconds).  Failure: Blinks between 100% White and Red 0x0000FF color for 3 seconds (at a rate of 200ms per flash), Once 3 seconds have passed, the LED should return to a Warm White LED at 100%  Success: Blinks between 100% White and Green 0x00FF00 color for 3 seconds (at a rate of 200ms per flash). Once 3 seconds have passed, the LED should return to a Warm White LED at 100%.", "ExclusionHelp": "Remove for exclusion 1. Assuming led bulb was added to controller. 2. Triggered by OFF -> ON -> OFF -> ON -> OFF -> ON (between 0.5-2 seconds each time). 3. LED Solid Purple/Violet Color (0xEE82EE) during the unpairing process. (Timeout is 10 seconds).  Failure: Blinks between 100% White and Red 0x0000FF color for 3 seconds (at a rate of 200ms per flash), Once 3 seconds have passed, the LED should return to the last color ( memory status(color cc set)) of LED Bulb.  Success: Blinks between 100% White and Blue 0x0000FF color for 3 seconds (at a rate of 200ms per flash). Once 3 seconds have passed, the LED should return to a Warm White LED at 100%.", "ResetHelp": "Reset the Device. 1. Assuming led bulb was added to controller and was power on. 2. RGBW bulb re-power 6 times (between 0.5-2 seconds each time). Note: ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON -> OFF -> ON 3. If the 6th power on, the led bulb change to Yellow color(into pairing process ), which means that the reset factory settings are successf. Using this action in case of the primary controller is missing or inoperable.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "LED Bulb 6:Multi-Colour", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAKAAAADICAIAAADgCn1NAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nO19SZMcyZXe89gjcl9qRRWqUAC6G91cmi1rklpO1Cw2B8lMB5m2HyGT/gBNB+k/6DKj85gOEkcco9Eoo81CjprNmW6yiUYDXQCqClWoysp9z8hYXAdHOl66R2QV0ERmZHW9Q9pLD3cP9/f5e597LB4kDENCCKUUAADgWr9i+suka7mSolFKFz7KrvU36MFhGMK1XF1RAIAQwv9f61dMv+bgKy7XHHzF9WsOvuJyzcFXXL/m4Csu1xx8xfVrDr7ics3BV1y/5uArLtccfMX1aw6+4nLNwVdcv+bgKy7XHHzF9WsOvuJyzcFXXL/m4Csu1xx8xfUrzsG+7wdBEAQBpRTHKmUimqYpirLAFr5p0SABo+wr6lyCIBiNRt5EgiCACRsRQjgtsaDFFFaJpmm6ruu6bpqmYRiYzBLSx9f34CXlYN5sSuloIkEQcCDJRFg2ATPBEOwvVwzDMAzDcRxN0yKLL5GQJX2zAQBc1+33+8PhEAAURcGIRsKJD0UGAJw5DMMwDDVNs23bcRwexpPQ96vPwf1+v9freZ6nqiqHFmeIBO9lnyV3JEh4DZTSMAyZYppmOp3mDr1EskweDAD9fr/T6VBK2RRJxlWAVnZoHLR5ZBZ4mvsrnQhzaAHmJNjkinAwpdR13VarFQSBqqrY2zAZQxRaLCXSfQWL4DNin+auzGC2LCuTychhI5mSdA8GAEppq9UaDodCQOb+GhlyL0yJOyoMhUiYKaXpdNqyLLlU0vSkczBzXEopdlwcilk4xQhxePAoEdxd9r8LRwAeVWEYBkGg63o2m024Hyfag3u9XrfbZY7LGZc5ECEkkoOF7vGIHdHzaXq+DE4c5nAiAJDL5djgS47dsJ5EDmZNarVao9GIXWnCQRIAGLog+aVQA5M4oo0M45eEGaZdOZPJGIYRWefCJYkeTCltNpu+72PSxVQShwSdvq4OyOIzMMZeHufQQthnw4tj7Pt+Op02TTM5NkwuB1NKG40Gmy2zyAySfSMBjgtFnLNnkG7kcJEpHwcMfCgMQ8/zUqkUnnYlRJJ1P5j5Ll4LAVrnMBGod0ZU5DUIiixCVfjUnO+Fi2U4p6Iouq4PBgPXdRduQ7EvyeFgSmmr1RqPx5qmYe/h2PCcgifhSTUeE7hUZIgWZMYhPKoEhyaTyQGL1ZlMRtf1GVXNWZLCwQDQ6/UGg4Ewq+I5X7Y4KjjH4SdnxoMjLo8sPHLQyeUUoSxrA7s1mcvlknPtOikc7Lpuu91WVVUOziyDjByelMVxpFA2cnAI4wymYSPo4uUMJ4bJ+o3de87lcl/VIr8nSQQHh2HI1rvYXvK0CBfE6IIEJ4/b3OGwIrsvv/8vUCxLkdMFh8Yp7Lff78/ZhnH64jmYUS9bFMmGYyLYF5eNVOTwHkfkwl85gMM0AQtn5znxwokFasdxkkDGi38mazQacXRlR5nRdAEG/Hf2lIoQwqMFTLs1nczYeSXCOk2oB7MyHyKqqlJKB4MBC9Rfaw6mlDYaDRzfeDrEc6fQDSHqYuFGxzVomha5YKWTK1PsWZ/IUwvjD8+58Ihh9bDnBS62wpuURXIwAAwGA0DPY7BEBlgkNkIlVBI52+wwIDCuqqqGYSiKwmZMM0rh5kX+VRTF8zzhAtz8dW02Ob05nQ3z0WgkT2EunBZgxsW4Xj684wyRTgnTceLCUSJM3FgoYh10HIefZf52XiQHM/fFwx+mwZPNKhAt9/VI34WZMEdOxyKDP17UzmgbTDMxIURVVd/3wzBUFGVRdn45g4i04JvTKaWu6+LrfxhgbEq5EpBmRnGZcZ5Iqo5rm9AG4UTykOLpMD28FEVh1y+/iq2+ir4wDh4OhxjXOFeLDN0i8U6DLYAB08AI8TZS59n4AilycOBSQvjBNfi+L4yYeeqL4WDmvoKtIWo1IkdgAVFZ4kZMHLRCIn+gALcWn46gK+QguTWdvgzOTjoej9nNxPljvBgOZtNLVVUBiYxZZESlk9v+kSJclJAr5DDEtU0+Kc9D4+85CkMQ0zZzYgbwnO0M7NWV+XMDnjxHelKkXIiu4KO4tziRveGiIMEBNjIwcHuxGRN3Yhpz20qI1WxZjAf03Gy+mHeTPM/DrsasEOltM/ogS6Q/yRXC5MYAu5MGEz9jcuGVH84jAsYwPZKE3rGrdfjonDxYZqw3qjN0eUokU8rFL4Mxd195oFB0AVI4xHXf9yPbwyuPG38zUnhZHqWvPgcDwHg8xm9sclRmeycgqECCPDIG4GzY2+Quzz7vbHSF9giZ8enk9Dno814HA/KVSMtiSCBmIRtZeZyDxpW6fOTnMfmSTRWK8F/WcfmMb1Sf9zoYMx8+JHsJT6FIYNqa3Hfx3SHcw8hwPQPpSBHYRGiMXGdcJXH3MN6oPm8OZgQsizz6ZkROblyWR7gHJes8BYMUGatpFI8K1cZNDoRYTdEEmwl+IR3mZfN5c7Dv+wQJxEskBnIMlBc5GEVcYSSWcahf2DD5b2RBflIckL6iDRPNwThMXShyTMaK8NS0EMMFRY7wckEhRQ68kVE68oyCzgvy7s/N5vPm4BkA48gsmFi2OAsAAgAQhdns0+G/kWXjwH4NIej9jKvJwZRSdiUI4kUwLpGuLMIkMstnwTXI3iwEfEER6hHOKIjML3INkaUiJ5hvVJ8rB8/wAwGPyBQmeG8UnnM8HvObNpE1zzBupAUEtOTH59iNXiHqXEjh3AJzs/lcr0XPvowsGALr2C6GYch3KTC6giLgJ58r8rwYOUKIvDsHjboqfiHGciPftD5XDqYxlwwFiWs0oNgo1C+DOqMSPGJkip1dVo4BM4bmjK5dTQ5mQ57GrCsEuo20OAc4zsWxCHmINCnjA252XBW6EwctSENZOPS14GC4hERmwzEz8qgMHkx7jBA/5aCNTyE7MT/KFawLeeLaj7lmPjZfwLXoSInLM9tTIyNzpDdHZruwAQIqM/qFqSducMzo15vTF3AtGuIlEj+Mx2x05Qqx0SPrkRNl4GVfl5t9YYpQ1dXk4EihUW8AQ4xTzoCZ63ExFqYDrKDA9GiQ23kh9cbBLI+Pedp8AfeDZ5hAURR8qUt26NkBgGcTYgYGXshDoyZfOIVOc3bkfELOPKOz8knfqD7XdTCZzJOxNwgDnF+ikl8ekYtjiRsKQrp8RqF5GEIm+NJbZLVCM8hEZuSfm83nysGypWQz8afg+L0Enp8JX2tBjNBpiUwREoUm0cmWDJGDTDhR5Hkhfoi/ht2+ij5XDmZvcHAvvNAXGcDsnXmWzrd/hWkLYne5zADHRWQGZW3Dj+Rxj+QF+WUs3ItI98UDSHjU8Cva8zL6XDl4xm2GSI/kpbD7zrhAGAewMGKEgrgGoeUUXXoTjvLTCYEdDwWYRhcXv5ocDAgtmPYhbDiQ8GalWHrkDUcM8OVbxavF4YRKc7RIDuZxBaZhk0cMHnkXPnzye9fnvQ6WfQi3Js4FCSFsfxaYtizPIMcG7vQwbWI6ifP4dDzyC+ly5Rw/uXKMLh5tuD2RdzmvDgcDgKqq+KkGzF5YkYGnEwLDIRr7kBylBTeC+AWbEDmFVrGBhbkTTwsi/R63EAt+OPAKcjAAaJrmui63pmAL2UAw7X8QtXziVcmDA3uS0B4ZGJZZHhPydooMYHxS2b7CGGUikMKbs/PCOFjXdW5Huf9CKRkeOtnDht2glT0YoiyLkeMRCw8ybHQMOaYG3Az2hLPs9Lg9MD3ImPvOzc5cnzcHc2AweLhN8iEBY/YiF0jo4o2McBHMuMIhLEIbYLIiZ/sq4r5wDxbG04V/hXeT5qMv4N0kVVVnb3Eii2Avz/PY/sz8KCGEBf/IUjgFpJGOd+YSohxIBAyTh3V4Zvw7o/GUUnmszEFfwPvBhmEMh0PBjoJFYBoG4eh4PLZtW5g5C0hEFpcDcuTMiNdAJu9M4No8z6PTzB2p8xTeHU3T5mlnuhAOppSaptnv94XIIbtapLcxYTQsA0ziH5kAhCv+xZtQCvQMEw4W2sAA5imRkzW5zcLLNXOz+QLeD9Z1nbeAdV6YoQgKnRaYOLGAH4c2zvWF5SwGJtILGQezzQh4fv50H275DGHZ2DeXcPrcbB7LHG9OKKXNZvPBgwfMPwCZm2dgCr7eC9MdkOsUFDlRAEOgK5m9eKLg2XJOiq5o4l86mRVms9m9vb24PS/fqCxsv+iTkxMAEOYdIBEwno5hE8vuS6e/a8SNy3VeCa+BeyofanjPWWGDB+ziMqHgQSDkZLuE27a9EDsvZo8OAHAcZzAYYNPglnEPECgQJqGYCQ+wfKcxkIaIjAc+FyCwyfTOwRgnmCl4wMnjlRAi7MAyT31h+2Sl02m2muT3d2W3AEkwipgLheAZGbEv/BtXs/x3huDKmei6LuwnPk99Yftk6bpuGAbbe4ZKd+UEhGTACJowxw0F4ez0ojkR3kFHKHWhE/MiAsZhGLLg/Er2+T3qi9yrMpPJ8L2EIUowupHOdxmnn30IhwQBWlwQ00FkPXio4UigKIppmjL8c9MXtlclADiOQyaXf7lFJNNdLHH1z068sIjcMP4r3F6MK8vc98J2vlF9wd9sSKfTgqUuAzO2O41iZYiaaePil4kHkee6ZFk6mVuwvYQvtMOb0xe2XzSTTCbTbrcjYyOTSGvCq4ziuOFCpWtPQoiLzBnZSJwNDwi2MdYC/QcW/s0GVVUdx+F3iIUMgsjgybbGMX9G8dmzrRllORnzdDw6BXdPpVJy2Tnri+RgpudyOfwUjozNjKB9eX+FVwc1rjGR6RhyJrquyy+qz19f/HeTDMNg88w4epMljhdnoMvzyNx84VlmV4j/8pQwDJn7Lta2sHAOZpEkl8udn59HTosuAzkOTZH56fREjJ1oxmiYLTgM4spxiNY0DX9tFjd1zvriv5sEALZts3ul3MkEC8JM75whkdjLZ4l0a4FoZV0Q3s4wDNPptJB+GTu8CX3xHMwkm83GPU0XN0LleiKhAmlmBK++5sb5hYJytYQQvjpauG0Xz8FM0uk0kSbAl4FhRh58SIABtyHSlePcNPIsWKGULnzti/XFczBM4l4mk+l2u9wJYFoE1pS5Ng4SmW4jvVyuRKBqeYjIpmONT6VSC8eV64ngYKZnMplOpwPxIkMl9wrnnAE5LoVnSTwD1imao804KctgWRZ/+Pnyfb/6HEwpVVXVtu24uMolLoTGpV84JmSvFdJl7pAzw8R98fRK7uP89aRwMNPZVIu3j0dCATaOJT8k/8adZcaggZjRIHN23DDSNG0hz8bO0BPBwVw3TVPXdf7UcSRUgrnj5rRyemRgwENE0CMbOSORTq5Nvl7f35CeIA5mejqdZrcfeBNnz7mEQMp/MRHKned/BXRlB5VHVVxLAIDfHEyOPRPEwUxJpVIzgJkdY2dgJucn0kPRkcUjz4vTeftldJOgJ4uDAUBRFDbVAiSR5ubwyEBiqHARoU45p5Aof/o27owY4IXbEOvJ4mCYXCjAbxlhI8JMNp2RjU+DcTbBWQXY+C+O/DgdkKiqirccTo49E8fBAGDbdqvVimsxjbpRjy3Lq4okb0AIxfl0ZPqMFEop+2Z83NhaoJ44DmaNE170mCECHrJT4myyh8V5rZBfOKNwXg7wV+/71edgJpiGZ5hewAmzslxnXKIsOB2/2RBXs6Io7IWrxdotUk8cB8PEIXiUxhlo1CMyuDhBYZxKd5DINJXKLwtFumykEEQE7GXlhdstUk8iB8PEJ4RvlAhTa0CgysNU6FdcEZwue+qMBRs/RCcP1y3cbpF6EjmYCX9eHAu2vnxI+L2M4Kk1mZbZ9eBDbOORC/t1zcFTumEYQgSOw5Wnvyq6uLjs2UKT4lqrqqoQ6hOlJ5GDmY4n0kTiXRlFhoHs9EwE8o7Mg6El0xQukz1P57uFJMRugp5EDubGjdzUgkyz42y0IqfiOA8WoeyM4YIPcYATYjdBTyIHz7AdFxmVSO8U8giHLqzzwlKUUmHHrqTpyeVgQJMXwf/iUOSZI/1SKCInxsVhoQFCU+V9lhKlJ5eDAQBfPeDplNLIyBkZZmnU0zaRZxQEUz5BfCwUYe6bZBsml4NhYj4swlQLC7cyjVkBzxBeFvv9jHNx4fF54baK0xPNwTwAQkzM5MI2j5QDaWSsljNg5ULhFVK0O+HCbRWnJ5qDYdqJMQZ4EIxGoz//8//Js3meR+mLDGyXq/HY40IpZV8hB4AwDNnIiJS4JvEG8BnWHOzw2nqiOZi5iOd5cjpPoZR+/PHf67peqZxns5kf//gn6Uyq0+68//63P/n0N5l0ulqrFfL509PTtbW10WhECEmlHM/zb97cPjw80g3Dtqw/+IMf8LPLY4jrMszz3//5VfVEczAAsKWwbFmeEgTB4dHhP//BDz766FemaXz43X+Uy2bHY+/P/vR//Kf//B81TWu12h9//PG9e/e+8Y33fvyXf/lHf/gHhCgHBwefP/jiP/z7fwsAh4eHbPNLXi2ReFdOgUlEkak6UXqi13CAQjT3XcGJP/vsfqlYOnh6MPa8drvtOClNVSnQ9Y0NtspKp1KuO7ZtmxDodfsPv3gEhPiBXygUWG1ra2vyUgckUCPdd+H2WXoOjjQ9Ttnf3/+TP/njf/JP//Ef/9Efuq5bq9VubG22252bN7d/8pOfHhwc/u8f/cUHH3wHgACQ9967F9JwfX21XqsDpZ988umjR49+9KP/M6P+GekL2f/5VfVXW07MXyil1WoVX81nsxumBEFQq9XW19cBQFGU8/NqpVLx/eDmznapWDyvVp+fPN/Z2UmnU/V6I5vNqqpyenrWarVu396zLOvw6MgduXfv3jEMQ9i4kJ+In4sl8vhBKTUMg20UNH+zXF4WtlflJXUAqNfrdPqDNPwonvvgJyBZnri93plwLBUkZCJ0WnjD+HnDMHQcB7/lvXBbReqJXgczwZFQlvF47Pt+pVLpdDqVSiUIwzAMK5UKWwuNx2O2wwsFOhgOWZHBYOD7/ng8ZsXZ7tNhGJ6enjLTCDvHM5E9NfJ7SknTF7Bf9Kt6MP8YA5/iAvLdx0+eFguFR19+ubqyYlnWycmnt27d+uTT36Yc+86d28+enQyGg71bu57nffHFow8+eL9YLP7853+1u7szGAxu3bpVq9XOz8+/973vPn16cOPGjf39x4PBgBCiaWoY0mKx0O321tfX2OP4GGPhbxJsFT2Lxlbjx5Kj8+AcR3WNekMhxLZshSi9bk/TNEKgkM+3Ws2HDx9pmmboOptd5/M55talUrHT6fZ6vcePH5fLZUrp6elZEITNZqPRaFmWFQQBIVCt1iqVSj6fT6X2IhuQ5Pv8XF8CDh4MBsyr5HkWAIxGI0VRwjDUdT0IAvZ2/XA4Mk1jNBqpqup5vqaphJDRyNU0le9PTAjxfd80zTCkhqEriuK6Y8exx+Oxruue52maNhq5uq7JI4xxcKFQwNZcuK2iPRgSwBMzdJiE6DgP3t9/TAjZ2Fiv1+qra2udTqdUKlmWef/+5+zzSr7vFwqFbrdnGMZw2H/77bcPDg7X1lZt2z4+PlYU1bbter3+wQffefbsWSaTOTg4yOcL2WyGEEJpWCgULcvkZ+eGE9qTBFstMQcLhzANP378ZGVlRVGUer0+HLmapjLHUlW13e4EQTAaDVVVazTqq6urnuc1Go0wDAFIs9nyPL9er7CtI7rd7unpGaU0l8s9evRoZ+cmABQKhXq9trW1BZKwiLJw+1yoL8E62PO8VquFr3jQiQBAr9e3bYt/y4i9odvtdnu9XiaToZTqut5qtSzLdt1RGIaZTIZtAut5XrPZzGQyg8HAMMx0OmUYRqPR0HWd7fCsqmq/3+92e9lsBoAoCuFv77OZQTabXZhdLi1JvxYNaDWCHZdnu3//vuM4lmWNx2PD0Pf29nRdPzp65jhOo9EwTSudTjebrVptf3194/T0eblcTqdTtm2fnVUGgyGlUKvV2+32nTt7+Xyh1+tXq1XHSRFC8vnc9vbWgwcPNzbW2QSAjRg5iiTEVkvMwXHMRwgpl8u+77PPnhWLRVbcsqwwDIrFIruUnU6ndV1XVXV7ezuTSWez2W63WywWLMtUFGVtbVXTVEVRTNMwDKNYLKyurtVqNV3XxuNxvpAbj8eOY7PrZTIZJ8dWkXrSZ9GEkDAM6/U6X5OwQzzD0dEzQkg2m+n1ep7np1KpIAja7fY777wNk+kuTEd1IolwJQvnx6cjkyVlEASGYeB31RNiqwgP5o2GiSRQ50YXpjZMXNdVlFy/P1BVjX0wi+1lxC5VCt0GSTjM8qFIEcB+033/inrSORjbUcaAUloul4Ig0HV9c3PDNE2+zGWOyzYqZvPwbrfLbvryW5A05gXiywgOJ8mx1fJxMNcxGNihf/WrX6+trRJCVFV1Xdc0zV6vZ9s2+7ihqqqZTPr09PTuW29VqzXXdYPAVxTVsqx8PreyssIrnwE2bg83nBBIEqsn/X4w9l0MNlNc1y2XS5RSNslyHKdYLNq2bZqmZVmmabJ9bFOptKaqnucVCgVd11OplO/77CPEkVEB4oVTshDVE6snfR3MpNlsBkGAyRimL1iyQ8LdXPxtFDqZbfHMXFgp/gsT75RL8fYEQZBKpS6/DcECZQk4+PJMSWNeF5P/ziiF0Y0sxUM0rjY5thL0pN8PFgiPG13GD6fjzDJOcg1xWMqluCwLByf9WjT3J3ZPHpDr8Dw4MxOMN0WPdnAR4jOJ2hJLGAfYy9kX+ZJjnxn6cqyDTdOs1+v4IWTZRyNdMNLdYQIwIKSFM3IDyRkopXxLrITYZ4a+HBzMNilqNpscY8FBYRpsXAOd+Vg1xgkjyi+A4we1eIV7e3tcT4J9ZuiEzxITLkEQHB4esguKMLmOwX/j3FfYnFiGkIMnbMEkh33u1o7jlEqluXT69yDLwcEAwLcLx56EgzMHD3ePSiLve0Um16J5uhDwhXS8J2Vy7BOnLwcHAwCl1DCM/f19vOkJD9GzwRYShXT2F9+UjAzRLL9lWWtra2Q6widZXw4OZnqpVHJdt9Vq8WgJk/g8wRgAIlwcJIzZL+ZXdFOSAEzFZ54tDMO9vT1+6iTY5EJ9aTiYyWAw6PV6zKMohYn/hpRSoEDQNSmYrGcoDSkVeVpRWFh+EQxUVWGBgdUZhiEfM4SwAfGiwnK5vLDOv5Yswf1gDAyTbrfzN3/1s8ODp71eL5vNsJuDmWy2UCimUmld18MwAELS6Vy706nVau1Wq9vtHT87rFROLcu8e/etmzt7qXTatixN01WV+N7YsixFVYfDwXnl7Oz0dDgaOY5jGuZwOCqVS2/fe++dd77FwF64HV5JXxoOxpLJZA3DOK+en52era2vbW/fNDXdcdLbN3fX19cJUYhC2q328fGzp0+fPPj888Ojo9Pnp71eL+WkdENvtroHR882Nzbz+Tx7v2hvb+/mzo6TynRazWar1Wi2Ou12NpsNgqBSqXz/+9/PZfMTV06EHS6vK5ycMF0lXGetJ0Du3XubhWsCwAB4/vzkv/3X/zIajl50jyiEKApRCCGmad65u7e6WlaUSYRGIf1nP/vpn/3pfwfy0jygEMsysrkco2uQrq4shb4094MFHYACgd/d//zGjS0AQgEYTa6vb/zrf/PvbNtmrx5R+oKpCSGe5+3vP7FtO5fLvaxkIh9++D2iqAAEKEOZAkC322+32zB92WThfX8lfTnuB8s6k8nUicJkyqWq6re++W2W8UV+NCrYa2fM6SdVvtBKpfL29u6LqthcmgJRFEopQVUkoe+vpC8ZB/PpMQDDAuAFti+8GKYAffmPxStUnETkBYprBYAXs2eYMlFy7HAZfZnWwVjn0EwweNkvBgcio5ciJOGjE9NM3PVFoH5pMjrh/oX3/ZX05bgfLOsAMI0IzocyTeM8NUQm4AmJMInRDE/RxxPQ968FB2fzedtxCCFUgneSiU28UEjnOaa/+A4gHOSjgZ/45eEk9P1rwcG5bN4wDApACHB/Y+RKCUzcL+IiSZzgszBhRTVN01RNRU97JccOl9GXlYNPT09azRZQ+oIsKfLiF9MjoJR5MyUAhBBVVVVVNU3DNE3d0BWFKArRdc2yTABoNOphrVatVvr9biGfzaQd27ZSqfT6+sb27u2V1fUl5eBlXQez7e9UTdN1XdU0wzRCGjabDW/sarrOHn+3LHP31q3yykq73Th48vj8vBKGYb6Qv3fvvbfefi+fLwDQbrftDgf9frNePanVzhuNxnjsm6ZpO5ZpWMVSOV9c2d29u/D+vrau/vCHP0xOPLm83u22VlfLt3Z3CAnb7fpw2Ot1W7Xz03a7ORx03dGQUqobhmlaRFHYFpXdTtfzg3Q6Y5h2u9WuVCpnZ6eddlvVja2t3RtbO5lsodPtP39+1u70PC/Yf/y42+2ur29sb+8uvL+vrb8CSyVKwiD48svPn+w/evLk8Wg0KpfLlmWnM5kbN7Y3NjfT6bTv+/V64+nTJ/fv3//iiwfPnh23Wm1CSD6fW11dLZeKlm1TShWilErFzc1N0zRrterBwdNmsxGGoaoqKysrN2/uFEsrW9u79+59Q9eNRXf6dWRZOZgoytr6jd98+g+V8/N+v396duZ7nm07W9tbGxsbtm27I7fZalTPz8/Pzwf9tqGTQt7RVM12TAW8Qb/te0NNU3XdGAy08wpVVLXf71Ma6Lo+dsdhQHu9/snJydnZ+Wg4XCmvrm/cSErfvw4cDADV8zNVgZVyAag3Hnu6aqgqHQ76zUa9b5h+EPQHw7EfeH7gusFg4A5HI1VRvQBUzUqlrUyukMmkLcsulUq3925v39xRNbV6Xnn06OHJyXEYBNlMxrKsVCq9c+t2sVROTt9fSV+m+8FY/+2nv/7oo18cHh5qmp5KpwzDWFtdv3P37trqmm4YQRC47rjb7dYb9Ua9VqtVK2en9XqNEJLL5XZv7b51914+X9tCjMoAAAgXSURBVFA1bdDrjL0x0GA4GjYa9Vr1fNAfGKZpGIZl2Sur65ub2996/0NClDfXlzfrwWQ518HDYX9tbd2xrV6v47qurkGvU/vtb5rpVDpfyJdKK5lMvlDIl1fKo+HO6emxQuho2Pf9wDQNx0m743G701EIUVQ1k82nHIcoSi5fI6CfjI9HI09VlYPDk053uLK2ScgS7IcVpy8rB3/ng+89fPi7s9MT3/c9L3TstJNKpdLp8srqSnnVSTm+H7TbrWfHzx7vP3568PT05LTVbvu+n0qljp6dra6u5vM5TdMURS2XS5ubW6ahn52dPtr/slKp0DBUVbVUKhUL+UatdvB0f2f3NveEhff9a8HBhmlt37z1+f3PPvvd/eFwlM1mfd93HGd399bmjSabZDUajcp55fT5Se280u93wmBs6JqpK2Hgdlq1Yb+laqpt24S6EI6BQK1WHfTaKgkDoIHvtdvN01Oz1x+MRkPbcdbWNhPS91fSl+a5aEEHgF6vZxrGzs0btWrV833LVIB6tepzbzzQdX3s+YP+oN1pd7td13U9z/f9IAypq3tmQImq207Gtm3dMHQzXSyv7+3dSaXTzUb9wYP7BwdPwzAsFgqmaRmGsba2nsnkktP3V9KXch1MKX3y+OGvfvXLp0+esC12AEh5ZeXtt9+5eXMnk8mGNOx1u9Va9fnJydlZpdGo16rVbrejKEo2k9na2tre3V1dWbMdO/C9sTtSVWXsjprNZq1W7fd77CFLXTPW1jc2t3a+/e0PNd3A9LZEsqwcfHJ8FPhBsVgkQAGoqiqqSg+fPqycHqbTmfLKaj5f3Lm589bdd9zx6PDp44/+3y/2v3wUhtQ01dJKcXNjM5vNE0J830unMoRQP/BB0VwvCALi+75lpwaDQbPV3d41NN2ASdBLQt+/Fhz8/nc+dFJOo16tnp93Oh1NUwzTsu1UvlAoFkupVJpSenJycnZ2dnR0eHJyUqtWO91e4Aftntvpuk+eHBWLRdtxbMu6cWNrd/eWYRj93vDs7Pzp0ydAqa7rqZSTSqVq1Uq9Xi2VVl6vnQvXl3UdDACNevVnP/3x3330d77nb2xsAoBt27fv3N3e3rYsa9AfnFerx8fPDg6eHj87bjQao9FI1/VsNpPNZm3bUhRCKTV0vVgqrq2t2ZbVajXPzk57vT6llFJqGObW9vbqynomm/3u9/9ZqbSakL6/mgfz2T83XPJ11vQgCCzbur27WzmvtJpVVVWGAz0M3Wb9jG1F2el0641Gv9cOQ09ViaGrikrCMPB9LwxNy7RM0zRM00ll0pnCja2te06q2+18+eWXR0eHvu8VC0VKSbVWK6+saJrOm5EcO1xGX1YOrp6f/frjXx4eHrQ7HU0ziaLn8rm9vds7O7eKxaKqqoPBoFqrHh0dqrpNFEPTW67rqqqaSadXV1c2b2yvra2xB98BQk3VPG9cPX/eabc0ld7a2dI0zTCMbC5fLK28++77Tip9zcFz1R88+Ozo6HAwGK2urqdSDgFQFMV1hw8ffBaEgWXZ5fLK5ub2rd1brus+3n/497/+uFI5I0Bsx966sfHNb31zc+umqqrDQb/f746GA8/3Aj+koBCiBhRUzfADOhqNTct2UumF9/e19WVdB7/19ruqQtzRqNNt93q9IKQqgK7ohm1Ztp0vFHL54nA0Oj45OTk5Pjg8OK9URqMxpdT16ZePD6r1Vj6fd2zbSaVu3ty5e/fdVMo5PDj4m7/96y++eEBDapmm73vbN2+ms7nhcGDbTnL6/kr6sq6DAeDs9ORv//r//sMnn/iet7W1pel6KpW6feetnZ0dx3H6/cHZ2emTJ08ePny4v79fqVSGw6Gu6/l8rpAv2I4dUhr4vqHr5XJpbW3dcezhcNhoNJqNOpuOlcvlVCq9urr2rfe/8+5772N6WyJZVg4GANOyPN/3PK/X6x6fHKuqms/n05mMZZmpVNr3vfHY1TQtm82wLQsHgwEh4DhOOpPe2NjY2NjM5/O242Sz2VKxpOuq67rn5+eVs+e9Xl/XtVwuZ9uO46RK5VVY2nXwkr0fjGUw6P/yFz/vdjr8E7GU0iAMXNcNg/Dee9945533bDtFCBmP3d999ulf/Oh/ddpt07K2t7f/xb/8V7u37qiqCkDCMGg2aw8f/G40GsHkdRhVVUzTMgw9X1i5+9a7C+3oV5JlXQczvdNuPXjw2+Gg77put9tp1Bv1er1er7fa7dFoaOh6Kp3RNW08HnvemBCFzY0Nw3AcO5PJZDJZXTcohAohhmFalmXZtmmapmlomk4UZWVlfWf3jrD2SEjfL6kvJQczYS0PguDs9LhRr/b73eFg2B/0+/3+oN8fjkbeeOxP3vAnhBBCXrzTr6qaqmqapum6ruuGYZgTMYwX/9KZ7MrqRjqdXVLq5bLcHox7Qik9PX1erdZUVSGEEJjsvcARmkKKwOQdM/qiNKUUFEXZ29sTdhlNQh+/jhwcKc+fP+90OnwnHiY4gzAymP4CXoCtra2l2EP28rKs7ybF6RsbG+zDOYJ/Y7AFnee5ceMG/prowvvye9GXmIMjhfni8fHxcDjEfixk437M8odhuLGxkfyPAb+GXAUOlvUwDM/Pz48OnxBFMU3TtmxKaavd6vf6pmVmM1nTsoDSTqcDQPP54vrGJvui1ku7JKYvX1G/ahyMxfe9Qb8/cofjset7vh94NAiJQlT1xfRZNwzHTpmWffUcl8vV9OBr/aUHXzEOvhZBlvVa9LV+Sf0qc/C1AMD/B04ffJuL1wCiAAAAAElFTkSuQmCC" }, "Event": "nodeNaming", "TimeStamp": 1579566891, "NodeManufacturerName": "Aeotec Limited", "NodeProductName": "ZWA002 LED Bulb 6 Multi-Color", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Multilevel Power Switch", "NodeSpecific": 1, "NodeManufacturerID": "0x0371", "NodeProductType": "0x0103", "NodeProductID": "0x0002", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1} +OpenZWave/1/node/39/instance/1/,{ "Instance": 1, "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/1407375551070225/,{ "Label": "Dimming Duration", "Value": 255, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 5, "Node": 39, "Genre": "System", "Help": "Duration taken when changing the Level of a Device", "ValueIDKey": 1407375551070225, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/659128337/,{ "Label": "Level", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 39, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 659128337, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/281475635839000/,{ "Label": "Bright", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 39, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475635839000, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/562950612549656/,{ "Label": "Dim", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 39, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950612549656, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/844425597648912/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 39, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425597648912, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/38/value/1125900574359569/,{ "Label": "Start Level", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 39, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900574359569, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/39/value/667533332/,{ "Label": "Switch All", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Off Enabled" }, { "Value": 2, "Label": "On Enabled" }, { "Value": 255, "Label": "On and Off Enabled" } ], "Selected": "On and Off Enabled" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 39, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 667533332, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/,{ "Instance": 1, "CommandClassId": 51, "CommandClass": "COMMAND_CLASS_COLOR", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/562950621151251/,{ "Label": "Color Channels", "Value": 31, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 2, "Node": 39, "Genre": "System", "Help": "Color Capabilities of the device", "ValueIDKey": 562950621151251, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/659341335/,{ "Label": "Color", "Value": "#000000FF00", "Units": "#RRGGBBWWCW", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 0, "Node": 39, "Genre": "User", "Help": "Color (in RGB format)", "ValueIDKey": 659341335, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/51/value/281475636051988/,{ "Label": "Color Index", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Cool White" }, { "Value": 2, "Label": "Warm White" }, { "Value": 3, "Label": "Red" }, { "Value": 4, "Label": "Lime" }, { "Value": 5, "Label": "Blue" }, { "Value": 6, "Label": "Yellow" }, { "Value": 7, "Label": "Cyan" }, { "Value": 8, "Label": "Magenta" }, { "Value": 9, "Label": "Silver" }, { "Value": 10, "Label": "Gray" }, { "Value": 11, "Label": "Maroon" }, { "Value": 12, "Label": "Olive" }, { "Value": 13, "Label": "Green" }, { "Value": 14, "Label": "Purple" }, { "Value": 15, "Label": "Teal" }, { "Value": 16, "Label": "Navy" }, { "Value": 17, "Label": "Custom" } ], "Selected": "Warm White" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_COLOR", "Index": 1, "Node": 39, "Genre": "User", "Help": "Preset Color", "ValueIDKey": 281475636051988, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/668434449/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 39, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 668434449, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/281475645145110/,{ "Label": "InstallerIcon", "Value": 1536, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 39, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475645145110, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/94/value/562950621855766/,{ "Label": "UserIcon", "Value": 1536, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 39, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950621855766, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/281475641245716/,{ "Label": "User custom mode LED animations", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Blink Colors in order mode" }, { "Value": 2, "Label": "Randomized blink color mode" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 39, "Genre": "Config", "Help": "User custom mode for LED animations", "ValueIDKey": 281475641245716, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/562950617956372/,{ "Label": "Strobe over Custom Color", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 39, "Genre": "Config", "Help": "Enable/Disable Strobe over Custom Color.", "ValueIDKey": 562950617956372, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/844425594667027/,{ "Label": "Set the rate of change to next color in Custom Mode", "Value": 50, "Units": "ms", "Min": 5, "Max": 8640000, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 39, "Genre": "Config", "Help": "Set the rate of change to next color in Custom Mode.", "ValueIDKey": 844425594667027, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/1125900571377681/,{ "Label": "Set color that LED Bulb blinks", "Value": 1, "Units": "", "Min": 1, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 39, "Genre": "Config", "Help": "Set color that LED Bulb blinks in Blink Mode.", "ValueIDKey": 1125900571377681, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/4503600291905553/,{ "Label": "Ramp rate when dimming using Multilevel Switch", "Value": 20, "Units": "100ms", "Min": 0, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 16, "Node": 39, "Genre": "Config", "Help": "Specifying the ramp rate when dimming using Multilevel Switch V1 CC in 100ms.", "ValueIDKey": 4503600291905553, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/22517998801387540/,{ "Label": "Notification", "Value": { "List": [ { "Value": 0, "Label": "Nothing" }, { "Value": 1, "Label": "Basic CC report" } ], "Selected": "Basic CC report" }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 80, "Node": 39, "Genre": "Config", "Help": "Enable to send notifications to associated devices (Group 1) when the state of LED Bulb is changed.", "ValueIDKey": 22517998801387540, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/22799473778098198/,{ "Label": "Warm White temperature", "Value": 2700, "Units": "k", "Min": 2700, "Max": 4999, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 81, "Node": 39, "Genre": "Config", "Help": "Adjusting the color temperature in warm white color component. available value: 2700k to 4999k", "ValueIDKey": 22799473778098198, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/112/value/23080948754808854/,{ "Label": "cold white temperature", "Value": 6500, "Units": "k", "Min": 5000, "Max": 6500, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 82, "Node": 39, "Genre": "Config", "Help": "Adjusting the color temperature in cold white color component. available value:5000k to 6500k", "ValueIDKey": 23080948754808854, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/668762131/,{ "Label": "Loaded Config Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 39, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 668762131, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/281475645472787/,{ "Label": "Config File Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 39, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475645472787, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/562950622183443/,{ "Label": "Latest Available Config File Revision", "Value": 3, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 39, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950622183443, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/844425598894103/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 39, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425598894103, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/114/value/1125900575604759/,{ "Label": "Serial Number", "Value": "00001cd6bda18c83", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 39, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900575604759, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/668778516/,{ "Label": "Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 39, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 668778516, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/281475645489169/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 39, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475645489169, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/562950622199832/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 39, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950622199832, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/844425598910481/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 39, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425598910481, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1125900575621140/,{ "Label": "Test Powerlevel", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "-1dB" }, { "Value": 2, "Label": "-2dB" }, { "Value": 3, "Label": "-3dB" }, { "Value": 4, "Label": "-4dB" }, { "Value": 5, "Label": "-5dB" }, { "Value": 6, "Label": "-6dB" }, { "Value": 7, "Label": "-7dB" }, { "Value": 8, "Label": "-8dB" }, { "Value": 9, "Label": "-9dB" } ], "Selected": "Normal" }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 39, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900575621140, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1407375552331798/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 39, "Genre": "System", "Help": "How Many Messages to send to the Note for the Test", "ValueIDKey": 1407375552331798, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1688850529042456/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 39, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850529042456, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/1970325505753112/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 39, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325505753112, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/2251800482463764/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed" }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 39, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800482463764, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/115/value/2533275459174422/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 39, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275459174422, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/669089815/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 39, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 669089815, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/281475645800471/,{ "Label": "Protocol Version", "Value": "4.38", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 39, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475645800471, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/134/value/562950622511127/,{ "Label": "Application Version", "Value": "2.00", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 39, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950622511127, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1579566891} +OpenZWave/1/node/39/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/43/,{ "Instance": 1, "CommandClassId": 43, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "TimeStamp": 1579566891} +OpenZWave/1/node/39/instance/1/commandclass/43/value/562950622511127/,{ "Label": "Scene", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 0, "Node": 7, "Genre": "User", "Help": "", "ValueIDKey": 122339347, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579630367} +OpenZWave/1/node/39/instance/1/commandclass/91/,{ "Instance": 1, "CommandClassId": 91, "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", "TimeStamp": 1579630630} +OpenZWave/1/node/39/instance/1/commandclass/91/value/281476005806100/,{ "Label": "Scene 1", "Value": { "List": [ { "Value": 0, "Label": "Inactive" }, { "Value": 1, "Label": "Pressed 1 Time" }, { "Value": 2, "Label": "Key Released" }, { "Value": 3, "Label": "Key Held down" } ], "Selected": "Inactive", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CENTRAL_SCENE", "Index": 1, "Node": 61, "Genre": "User", "Help": "", "ValueIDKey": 281476005806100, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1579640710} \ No newline at end of file diff --git a/tests/fixtures/zwave_mqtt/switch.json b/tests/fixtures/zwave_mqtt/switch.json new file mode 100644 index 00000000000..0d3fc37e9b2 --- /dev/null +++ b/tests/fixtures/zwave_mqtt/switch.json @@ -0,0 +1,25 @@ +{ + "topic": "OpenZWave/1/node/32/instance/1/commandclass/37/value/541671440/", + "payload": { + "Label": "Switch", + "Value": false, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "Bool", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SWITCH_BINARY", + "Index": 0, + "Node": 32, + "Genre": "User", + "Help": "Turn On/Off Device", + "ValueIDKey": 541671440, + "ReadOnly": false, + "WriteOnly": false, + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Event": "valueAdded", + "TimeStamp": 1579566891 + } +} From 6afb42bf7a15888a49ec763bedc121cf32f6a1ee Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Sun, 3 May 2020 03:03:54 +0200 Subject: [PATCH 228/511] Improve UPnP configuration flow (#34737) --- .../components/discovery/__init__.py | 2 - homeassistant/components/upnp/__init__.py | 53 +++-- homeassistant/components/upnp/config_flow.py | 189 +++++++++++++++++- homeassistant/components/upnp/const.py | 7 + homeassistant/components/upnp/device.py | 25 ++- homeassistant/components/upnp/manifest.json | 10 +- homeassistant/components/upnp/strings.json | 17 +- .../components/upnp/translations/en.json | 21 +- homeassistant/generated/ssdp.py | 8 + tests/components/upnp/mock_device.py | 77 +++++++ tests/components/upnp/test_config_flow.py | 124 ++++++++++++ tests/components/upnp/test_init.py | 120 ++++------- 12 files changed, 514 insertions(+), 139 deletions(-) create mode 100644 tests/components/upnp/mock_device.py create mode 100644 tests/components/upnp/test_config_flow.py diff --git a/homeassistant/components/discovery/__init__.py b/homeassistant/components/discovery/__init__.py index 227995db971..87c0e41533b 100644 --- a/homeassistant/components/discovery/__init__.py +++ b/homeassistant/components/discovery/__init__.py @@ -32,7 +32,6 @@ SERVICE_FREEBOX = "freebox" SERVICE_HASS_IOS_APP = "hass_ios" SERVICE_HASSIO = "hassio" SERVICE_HEOS = "heos" -SERVICE_IGD = "igd" SERVICE_KONNECTED = "konnected" SERVICE_MOBILE_APP = "hass_mobile_app" SERVICE_NETGEAR = "netgear_router" @@ -48,7 +47,6 @@ SERVICE_XIAOMI_GW = "xiaomi_gw" CONFIG_ENTRY_HANDLERS = { SERVICE_DAIKIN: "daikin", SERVICE_TELLDUSLIVE: "tellduslive", - SERVICE_IGD: "upnp", } SERVICE_HANDLERS = { diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 4d599be88b1..f183daa4ae9 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -19,6 +19,12 @@ from .const import ( CONF_HASS, CONF_LOCAL_IP, CONF_PORTS, + CONFIG_ENTRY_ST, + CONFIG_ENTRY_UDN, + DISCOVERY_LOCATION, + DISCOVERY_ST, + DISCOVERY_UDN, + DISCOVERY_USN, DOMAIN, LOGGER as _LOGGER, ) @@ -89,40 +95,41 @@ async def async_discover_and_construct( """Discovery devices and construct a Device for one.""" # pylint: disable=invalid-name discovery_infos = await Device.async_discover(hass) + _LOGGER.debug("Discovered devices: %s", discovery_infos) if not discovery_infos: _LOGGER.info("No UPnP/IGD devices discovered") return None if udn: - # get the discovery info with specified UDN - _LOGGER.debug("Discovery_infos: %s", discovery_infos) - filtered = [di for di in discovery_infos if di["udn"] == udn] + # Get the discovery info with specified UDN/ST. + filtered = [di for di in discovery_infos if di[DISCOVERY_UDN] == udn] if st: - _LOGGER.debug("Filtering on ST: %s", st) - filtered = [di for di in discovery_infos if di["st"] == st] + filtered = [di for di in discovery_infos if di[DISCOVERY_ST] == st] if not filtered: _LOGGER.warning( - 'Wanted UPnP/IGD device with UDN "%s" not found, ' "aborting", udn + 'Wanted UPnP/IGD device with UDN "%s" not found, aborting', udn ) return None - # ensure we're always taking the latest - filtered = sorted(filtered, key=itemgetter("st"), reverse=True) + + # Ensure we're always taking the latest, if we filtered only on UDN. + filtered = sorted(filtered, key=itemgetter(DISCOVERY_ST), reverse=True) discovery_info = filtered[0] else: - # get the first/any + # Get the first/any. discovery_info = discovery_infos[0] if len(discovery_infos) > 1: device_name = discovery_info.get( - "usn", discovery_info.get("ssdp_description", "") + DISCOVERY_USN, discovery_info.get(DISCOVERY_LOCATION, "") ) _LOGGER.info("Detected multiple UPnP/IGD devices, using: %s", device_name) - ssdp_description = discovery_info["ssdp_description"] - return await Device.async_create_device(hass, ssdp_description) + location = discovery_info[DISCOVERY_LOCATION] + return await Device.async_create_device(hass, location) async def async_setup(hass: HomeAssistantType, config: ConfigType): """Set up UPnP component.""" + _LOGGER.debug("async_setup, config: %s", config) conf_default = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN] conf = config.get(DOMAIN, conf_default) local_ip = await hass.async_add_executor_job(get_local_ip) @@ -133,7 +140,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): "ports": conf.get(CONF_PORTS), } - if conf is not None: + # Only start if set up via configuration.yaml. + if DOMAIN in config: hass.async_create_task( hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT} @@ -145,23 +153,26 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) -> bool: """Set up UPnP/IGD device from a config entry.""" + _LOGGER.debug("async_setup_entry, config_entry: %s", config_entry.data) domain_data = hass.data[DOMAIN] conf = domain_data["config"] # discover and construct - udn = config_entry.data.get("udn") - st = config_entry.data.get("st") # pylint: disable=invalid-name + udn = config_entry.data.get(CONFIG_ENTRY_UDN) + st = config_entry.data.get(CONFIG_ENTRY_ST) # pylint: disable=invalid-name device = await async_discover_and_construct(hass, udn, st) if not device: _LOGGER.info("Unable to create UPnP/IGD, aborting") raise ConfigEntryNotReady - # 'register'/save UDN + ST + # 'register'/save device hass.data[DOMAIN]["devices"][device.udn] = device - hass.config_entries.async_update_entry( - entry=config_entry, - data={**config_entry.data, "udn": device.udn, "st": device.device_type}, - ) + + # Ensure entry has proper unique_id. + if config_entry.unique_id != device.unique_id: + hass.config_entries.async_update_entry( + entry=config_entry, unique_id=device.unique_id, + ) # create device registry entry device_registry = await dr.async_get_registry(hass) @@ -211,7 +222,7 @@ async def async_unload_entry( hass: HomeAssistantType, config_entry: ConfigEntry ) -> bool: """Unload a UPnP/IGD device from a config entry.""" - udn = config_entry.data["udn"] + udn = config_entry.data[CONFIG_ENTRY_UDN] device = hass.data[DOMAIN]["devices"][udn] # remove port mapping diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 1601595b6a9..4701f21633c 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -1,10 +1,187 @@ """Config flow for UPNP.""" -from homeassistant import config_entries -from homeassistant.helpers import config_entry_flow +from typing import Mapping, Optional -from .const import DOMAIN +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components import ssdp + +from .const import ( # pylint: disable=unused-import + CONFIG_ENTRY_ST, + CONFIG_ENTRY_UDN, + DISCOVERY_LOCATION, + DISCOVERY_NAME, + DISCOVERY_ST, + DISCOVERY_UDN, + DISCOVERY_USN, + DOMAIN, + LOGGER as _LOGGER, +) from .device import Device -config_entry_flow.register_discovery_flow( - DOMAIN, "UPnP/IGD", Device.async_discover, config_entries.CONN_CLASS_LOCAL_POLL -) + +class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a UPnP/IGD config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + # Paths: + # - ssdp(discovery_info) --> ssdp_confirm(None) --> ssdp_confirm({}) --> create_entry() + # - user(None): scan --> user({...}) --> create_entry() + # - import(None) --> create_entry() + + def __init__(self): + """Initialize the UPnP/IGD config flow.""" + self._discoveries: Mapping = None + + async def async_step_user(self, user_input: Optional[Mapping] = None): + """Handle a flow start.""" + _LOGGER.debug("async_step_user: user_input: %s", user_input) + # This uses DISCOVERY_USN as the identifier for the device. + + if user_input is not None: + # Ensure wanted device was discovered. + matching_discoveries = [ + discovery + for discovery in self._discoveries + if discovery[DISCOVERY_USN] == user_input["usn"] + ] + if not matching_discoveries: + return self.async_abort(reason="no_devices_discovered") + + discovery = matching_discoveries[0] + await self.async_set_unique_id( + discovery[DISCOVERY_USN], raise_on_progress=False + ) + return await self._async_create_entry_from_data(discovery) + + # Discover devices. + discoveries = await Device.async_discover(self.hass) + + # Store discoveries which have not been configured, add name for each discovery. + current_usns = {entry.unique_id for entry in self._async_current_entries()} + self._discoveries = [ + { + **discovery, + DISCOVERY_NAME: await self._async_get_name_for_discovery(discovery), + } + for discovery in discoveries + if discovery[DISCOVERY_USN] not in current_usns + ] + + # Ensure anything to add. + if not self._discoveries: + return self.async_abort(reason="no_devices_found") + + data_schema = vol.Schema( + { + vol.Required("usn"): vol.In( + { + discovery[DISCOVERY_USN]: discovery[DISCOVERY_NAME] + for discovery in self._discoveries + } + ), + } + ) + return self.async_show_form(step_id="user", data_schema=data_schema,) + + async def async_step_import(self, import_info: Optional[Mapping]): + """Import a new UPnP/IGD device as a config entry. + + This flow is triggered by `async_setup`. If no device has been + configured before, find any device and create a config_entry for it. + Otherwise, do nothing. + """ + _LOGGER.debug("async_step_import: import_info: %s", import_info) + + if import_info is None: + # Landed here via configuration.yaml entry. + # Any device already added, then abort. + if self._async_current_entries(): + _LOGGER.debug("aborting, already configured") + return self.async_abort(reason="already_configured") + + # Test if import_info isn't already configured. + if import_info is not None and any( + import_info["udn"] == entry.data[CONFIG_ENTRY_UDN] + and import_info["st"] == entry.data[CONFIG_ENTRY_ST] + for entry in self._async_current_entries() + ): + return self.async_abort(reason="already_configured") + + # Discover devices. + self._discoveries = await Device.async_discover(self.hass) + + # Ensure anything to add. If not, silently abort. + if not self._discoveries: + _LOGGER.info("No UPnP devices discovered, aborting.") + return self.async_abort(reason="no_devices_found") + + discovery = self._discoveries[0] + return await self._async_create_entry_from_data(discovery) + + async def async_step_ssdp(self, discovery_info: Mapping): + """Handle a discovered UPnP/IGD device. + + This flow is triggered by the SSDP component. It will check if the + host is already configured and delegate to the import step if not. + """ + _LOGGER.debug("async_step_ssdp: discovery_info: %s", discovery_info) + + # Ensure not already configuring/configured. + udn = discovery_info[ssdp.ATTR_UPNP_UDN] + st = discovery_info[ssdp.ATTR_SSDP_ST] # pylint: disable=invalid-name + usn = f"{udn}::{st}" + await self.async_set_unique_id(usn) + self._abort_if_unique_id_configured() + + # Store discovery. + name = discovery_info.get("friendlyName", "") + discovery = { + DISCOVERY_UDN: udn, + DISCOVERY_ST: st, + DISCOVERY_NAME: name, + } + self._discoveries = [discovery] + + # Ensure user recognizable. + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context["title_placeholders"] = { + "name": name, + } + + return await self.async_step_ssdp_confirm() + + async def async_step_ssdp_confirm(self, user_input: Optional[Mapping] = None): + """Confirm integration via SSDP.""" + _LOGGER.debug("async_step_ssdp_confirm: user_input: %s", user_input) + if user_input is None: + return self.async_show_form(step_id="ssdp_confirm") + + discovery = self._discoveries[0] + return await self._async_create_entry_from_data(discovery) + + async def _async_create_entry_from_data(self, discovery: Mapping): + """Create an entry from own _data.""" + _LOGGER.debug("_async_create_entry_from_data: discovery: %s", discovery) + # Get name from device, if not found already. + if DISCOVERY_NAME not in discovery and DISCOVERY_LOCATION in discovery: + discovery[DISCOVERY_NAME] = await self._async_get_name_for_discovery( + discovery + ) + + title = discovery.get(DISCOVERY_NAME, "") + data = { + CONFIG_ENTRY_UDN: discovery[DISCOVERY_UDN], + CONFIG_ENTRY_ST: discovery[DISCOVERY_ST], + } + return self.async_create_entry(title=title, data=data) + + async def _async_get_name_for_discovery(self, discovery: Mapping): + """Get the name of the device from a discovery.""" + _LOGGER.debug("_async_get_name_for_discovery: discovery: %s", discovery) + device = await Device.async_create_device( + self.hass, discovery[DISCOVERY_LOCATION] + ) + return device.name diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py index 80b5b718bbb..ee3b5873310 100644 --- a/homeassistant/components/upnp/const.py +++ b/homeassistant/components/upnp/const.py @@ -20,3 +20,10 @@ DATA_PACKETS = "packets" DATA_RATE_PACKETS_PER_SECOND = f"{DATA_PACKETS}/{TIME_SECONDS}" KIBIBYTE = 1024 UPDATE_INTERVAL = timedelta(seconds=30) +DISCOVERY_NAME = "name" +DISCOVERY_LOCATION = "location" +DISCOVERY_ST = "st" +DISCOVERY_UDN = "udn" +DISCOVERY_USN = "usn" +CONFIG_ENTRY_UDN = "udn" +CONFIG_ENTRY_ST = "st" diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index 73ae06d9945..ec7753bce87 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -1,7 +1,7 @@ """Home Assistant representation of an UPnP/IGD.""" import asyncio from ipaddress import IPv4Address -from typing import Mapping +from typing import List, Mapping import aiohttp from async_upnp_client import UpnpError, UpnpFactory @@ -16,6 +16,10 @@ from .const import ( BYTES_RECEIVED, BYTES_SENT, CONF_LOCAL_IP, + DISCOVERY_LOCATION, + DISCOVERY_ST, + DISCOVERY_UDN, + DISCOVERY_USN, DOMAIN, LOGGER as _LOGGER, PACKETS_RECEIVED, @@ -33,7 +37,7 @@ class Device: self._mapped_ports = [] @classmethod - async def async_discover(cls, hass: HomeAssistantType): + async def async_discover(cls, hass: HomeAssistantType) -> List[Mapping]: """Discover UPnP/IGD devices.""" _LOGGER.debug("Discovering UPnP/IGD devices") local_ip = None @@ -47,9 +51,11 @@ class Device: # add extra info and store devices devices = [] for discovery_info in discovery_infos: - discovery_info["udn"] = discovery_info["_udn"] - discovery_info["ssdp_description"] = discovery_info["location"] - discovery_info["source"] = "async_upnp_client" + discovery_info[DISCOVERY_UDN] = discovery_info["_udn"] + discovery_info[DISCOVERY_ST] = discovery_info["st"] + discovery_info[DISCOVERY_LOCATION] = discovery_info["location"] + usn = f"{discovery_info[DISCOVERY_UDN]}::{discovery_info[DISCOVERY_ST]}" + discovery_info[DISCOVERY_USN] = usn _LOGGER.debug("Discovered device: %s", discovery_info) devices.append(discovery_info) @@ -57,7 +63,7 @@ class Device: return devices @classmethod - async def async_create_device(cls, hass: HomeAssistantType, ssdp_description: str): + async def async_create_device(cls, hass: HomeAssistantType, ssdp_location: str): """Create UPnP/IGD device.""" # build async_upnp_client requester session = async_get_clientsession(hass) @@ -65,7 +71,7 @@ class Device: # create async_upnp_client device factory = UpnpFactory(requester, disable_state_variable_validation=True) - upnp_device = await factory.async_create_device(ssdp_description) + upnp_device = await factory.async_create_device(ssdp_location) igd_device = IgdDevice(upnp_device, None) @@ -96,6 +102,11 @@ class Device: """Get the device type.""" return self._igd_device.device_type + @property + def unique_id(self) -> str: + """Get the unique id.""" + return f"{self.udn}::{self.device_type}" + def __str__(self) -> str: """Get string representation.""" return f"IGD Device: {self.name}/{self.udn}" diff --git a/homeassistant/components/upnp/manifest.json b/homeassistant/components/upnp/manifest.json index 2f6e5de5884..e3b30cec9a4 100644 --- a/homeassistant/components/upnp/manifest.json +++ b/homeassistant/components/upnp/manifest.json @@ -5,5 +5,13 @@ "documentation": "https://www.home-assistant.io/integrations/upnp", "requirements": ["async-upnp-client==0.14.13"], "dependencies": [], - "codeowners": ["@StevenLooman"] + "codeowners": ["@StevenLooman"], + "ssdp": [ + { + "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" + }, + { + "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2" + } + ] } diff --git a/homeassistant/components/upnp/strings.json b/homeassistant/components/upnp/strings.json index 5ad90b2c0cb..9f2f6978341 100644 --- a/homeassistant/components/upnp/strings.json +++ b/homeassistant/components/upnp/strings.json @@ -1,25 +1,22 @@ { "config": { + "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Do you want to set up UPnP/IGD?" + "init": { + }, + "ssdp_confirm": { + "description": "Do you want to set up this UPnP/IGD device?" }, "user": { - "title": "Configuration options", "data": { - "enable_port_mapping": "Enable port mapping for Home Assistant", - "enable_sensors": "Add traffic sensors", - "igd": "UPnP/IGD" + "usn": "Device" } } }, "abort": { "already_configured": "UPnP/IGD is already configured", - "incomplete_device": "Ignoring incomplete UPnP device", "no_devices_discovered": "No UPnP/IGDs discovered", - "no_devices_found": "No UPnP/IGD devices found on the network.", - "no_sensors_or_port_mapping": "Enable at least sensors or port mapping", - "single_instance_allowed": "Only a single configuration of UPnP/IGD is necessary." + "no_devices_found": "No UPnP/IGD devices found on the network." } } } diff --git a/homeassistant/components/upnp/translations/en.json b/homeassistant/components/upnp/translations/en.json index 6da89c0e3d6..124ccfdd17d 100644 --- a/homeassistant/components/upnp/translations/en.json +++ b/homeassistant/components/upnp/translations/en.json @@ -1,28 +1,21 @@ { "config": { + "flow_title": "UPnP/IGD: {name}", "abort": { "already_configured": "UPnP/IGD is already configured", - "incomplete_device": "Ignoring incomplete UPnP device", "no_devices_discovered": "No UPnP/IGDs discovered", - "no_devices_found": "No UPnP/IGD devices found on the network.", - "no_sensors_or_port_mapping": "Enable at least sensors or port mapping", - "single_instance_allowed": "Only a single configuration of UPnP/IGD is necessary." + "no_devices_found": "No UPnP/IGD devices found on the network." }, "step": { - "confirm": { - "description": "Do you want to set up UPnP/IGD?", - "title": "UPnP/IGD" - }, "init": { - "title": "UPnP/IGD" + }, + "ssdp_confirm": { + "description": "Do you want to set up this UPnP/IGD device?" }, "user": { "data": { - "enable_port_mapping": "Enable port mapping for Home Assistant", - "enable_sensors": "Add traffic sensors", - "igd": "UPnP/IGD" - }, - "title": "Configuration options" + "usn": "Device" + } } } } diff --git a/homeassistant/generated/ssdp.py b/homeassistant/generated/ssdp.py index 5dbef37d9bf..f46ba1611a8 100644 --- a/homeassistant/generated/ssdp.py +++ b/homeassistant/generated/ssdp.py @@ -81,6 +81,14 @@ SSDP = { "manufacturer": "Synology" } ], + "upnp": [ + { + "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:1" + }, + { + "st": "urn:schemas-upnp-org:device:InternetGatewayDevice:2" + } + ], "wemo": [ { "manufacturer": "Belkin International Inc." diff --git a/tests/components/upnp/mock_device.py b/tests/components/upnp/mock_device.py new file mode 100644 index 00000000000..17d9b5659c5 --- /dev/null +++ b/tests/components/upnp/mock_device.py @@ -0,0 +1,77 @@ +"""Mock device for testing purposes.""" + +from typing import Mapping + +from homeassistant.components.upnp.const import ( + BYTES_RECEIVED, + BYTES_SENT, + PACKETS_RECEIVED, + PACKETS_SENT, + TIMESTAMP, +) +from homeassistant.components.upnp.device import Device +import homeassistant.util.dt as dt_util + + +class MockDevice(Device): + """Mock device for Device.""" + + def __init__(self, udn): + """Initialize mock device.""" + igd_device = object() + super().__init__(igd_device) + self._udn = udn + self.added_port_mappings = [] + self.removed_port_mappings = [] + + @classmethod + async def async_create_device(cls, hass, ssdp_location): + """Return self.""" + return cls("UDN") + + @property + def udn(self) -> str: + """Get the UDN.""" + return self._udn + + @property + def manufacturer(self) -> str: + """Get manufacturer.""" + return "mock-manufacturer" + + @property + def name(self) -> str: + """Get name.""" + return "mock-name" + + @property + def model_name(self) -> str: + """Get the model name.""" + return "mock-model-name" + + @property + def device_type(self) -> str: + """Get the device type.""" + return "urn:schemas-upnp-org:device:InternetGatewayDevice:1" + + async def _async_add_port_mapping( + self, external_port: int, local_ip: str, internal_port: int + ) -> None: + """Add a port mapping.""" + entry = [external_port, local_ip, internal_port] + self.added_port_mappings.append(entry) + + async def _async_delete_port_mapping(self, external_port: int) -> None: + """Remove a port mapping.""" + entry = external_port + self.removed_port_mappings.append(entry) + + async def async_get_traffic_data(self) -> Mapping[str, any]: + """Get traffic data.""" + return { + TIMESTAMP: dt_util.utcnow(), + BYTES_RECEIVED: 0, + BYTES_SENT: 0, + PACKETS_RECEIVED: 0, + PACKETS_SENT: 0, + } diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py new file mode 100644 index 00000000000..c6e383bae55 --- /dev/null +++ b/tests/components/upnp/test_config_flow.py @@ -0,0 +1,124 @@ +"""Test UPnP/IGD config flow.""" + +from homeassistant import config_entries, data_entry_flow +from homeassistant.components import ssdp +from homeassistant.components.upnp.const import ( + DISCOVERY_LOCATION, + DISCOVERY_ST, + DISCOVERY_UDN, + DISCOVERY_USN, + DOMAIN, +) +from homeassistant.components.upnp.device import Device +from homeassistant.helpers.typing import HomeAssistantType + +from .mock_device import MockDevice + +from tests.async_mock import AsyncMock, patch + + +async def test_flow_ssdp_discovery(hass: HomeAssistantType): + """Test config flow: discovered + configured through ssdp.""" + udn = "uuid:device_1" + mock_device = MockDevice(udn) + discovery_infos = [ + { + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_LOCATION: "dummy", + } + ] + with patch.object( + Device, "async_create_device", AsyncMock(return_value=mock_device) + ), patch.object(Device, "async_discover", AsyncMock(return_value=discovery_infos)): + # Discovered via step ssdp. + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_SSDP}, + data={ + ssdp.ATTR_SSDP_ST: mock_device.device_type, + ssdp.ATTR_UPNP_UDN: mock_device.udn, + "friendlyName": mock_device.name, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "ssdp_confirm" + + # Confirm via step ssdp_confirm. + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == mock_device.name + assert result["data"] == { + "st": mock_device.device_type, + "udn": mock_device.udn, + } + + +async def test_flow_user(hass: HomeAssistantType): + """Test config flow: discovered + configured through user.""" + udn = "uuid:device_1" + mock_device = MockDevice(udn) + usn = f"{mock_device.udn}::{mock_device.device_type}" + discovery_infos = [ + { + DISCOVERY_USN: usn, + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_LOCATION: "dummy", + } + ] + + with patch.object( + Device, "async_create_device", AsyncMock(return_value=mock_device) + ), patch.object(Device, "async_discover", AsyncMock(return_value=discovery_infos)): + # Discovered via step user. + 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["step_id"] == "user" + + # Confirmed via step user. + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"usn": usn}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == mock_device.name + assert result["data"] == { + "st": mock_device.device_type, + "udn": mock_device.udn, + } + + +async def test_flow_config(hass: HomeAssistantType): + """Test config flow: discovered + configured through configuration.yaml.""" + udn = "uuid:device_1" + mock_device = MockDevice(udn) + usn = f"{mock_device.udn}::{mock_device.device_type}" + discovery_infos = [ + { + DISCOVERY_USN: usn, + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_LOCATION: "dummy", + } + ] + + with patch.object( + Device, "async_create_device", AsyncMock(return_value=mock_device) + ), patch.object(Device, "async_discover", AsyncMock(return_value=discovery_infos)): + # Discovered via step import. + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == mock_device.name + assert result["data"] == { + "st": mock_device.device_type, + "udn": mock_device.udn, + } diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py index 7c43c24cdc3..7d32c37c9ef 100644 --- a/tests/components/upnp/test_init.py +++ b/tests/components/upnp/test_init.py @@ -3,91 +3,49 @@ from ipaddress import IPv4Address from homeassistant.components import upnp +from homeassistant.components.upnp.const import ( + DISCOVERY_LOCATION, + DISCOVERY_ST, + DISCOVERY_UDN, +) from homeassistant.components.upnp.device import Device from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.setup import async_setup_component -from tests.async_mock import patch -from tests.common import MockConfigEntry, mock_coro +from .mock_device import MockDevice - -class MockDevice(Device): - """Mock device for Device.""" - - def __init__(self, udn): - """Initialize mock device.""" - igd_device = object() - super().__init__(igd_device) - self._udn = udn - self.added_port_mappings = [] - self.removed_port_mappings = [] - - @classmethod - async def async_create_device(cls, hass, ssdp_description): - """Return self.""" - return cls("UDN") - - @property - def udn(self) -> str: - """Get the UDN.""" - return self._udn - - @property - def manufacturer(self) -> str: - """Get manufacturer.""" - return "mock-manufacturer" - - @property - def name(self) -> str: - """Get name.""" - return "mock-name" - - @property - def model_name(self) -> str: - """Get the model name.""" - return "mock-model-name" - - @property - def device_type(self) -> str: - """Get the device type.""" - return "urn:schemas-upnp-org:device:InternetGatewayDevice:1" - - async def _async_add_port_mapping( - self, external_port: int, local_ip: str, internal_port: int - ) -> None: - """Add a port mapping.""" - entry = [external_port, local_ip, internal_port] - self.added_port_mappings.append(entry) - - async def _async_delete_port_mapping(self, external_port: int) -> None: - """Remove a port mapping.""" - entry = external_port - self.removed_port_mappings.append(entry) +from tests.async_mock import AsyncMock, patch +from tests.common import MockConfigEntry async def test_async_setup_entry_default(hass): """Test async_setup_entry.""" udn = "uuid:device_1" - entry = MockConfigEntry(domain=upnp.DOMAIN) + mock_device = MockDevice(udn) + discovery_infos = [ + { + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_LOCATION: "http://192.168.1.1/desc.xml", + } + ] + entry = MockConfigEntry( + domain=upnp.DOMAIN, data={"udn": mock_device.udn, "st": mock_device.device_type} + ) config = { # no upnp } - with patch.object(Device, "async_create_device") as create_device, patch.object( - Device, "async_discover", return_value=mock_coro([]) - ) as async_discover: + async_discover = AsyncMock(return_value=[]) + with patch.object( + Device, "async_create_device", AsyncMock(return_value=mock_device) + ), patch.object(Device, "async_discover", async_discover): + # initialisation of component, no device discovered await async_setup_component(hass, "upnp", config) await hass.async_block_till_done() - # mock homeassistant.components.upnp.device.Device - mock_device = MockDevice(udn) - discovery_infos = [ - {"udn": udn, "ssdp_description": "http://192.168.1.1/desc.xml"} - ] - - create_device.return_value = mock_device + # loading of config_entry, device discovered async_discover.return_value = discovery_infos - assert await upnp.async_setup_entry(hass, entry) is True # ensure device is stored/used @@ -105,7 +63,17 @@ async def test_async_setup_entry_port_mapping(hass): """Test async_setup_entry.""" # pylint: disable=invalid-name udn = "uuid:device_1" - entry = MockConfigEntry(domain=upnp.DOMAIN) + mock_device = MockDevice(udn) + discovery_infos = [ + { + DISCOVERY_UDN: mock_device.udn, + DISCOVERY_ST: mock_device.device_type, + DISCOVERY_LOCATION: "http://192.168.1.1/desc.xml", + } + ] + entry = MockConfigEntry( + domain=upnp.DOMAIN, data={"udn": mock_device.udn, "st": mock_device.device_type} + ) config = { "http": {}, @@ -115,21 +83,17 @@ async def test_async_setup_entry_port_mapping(hass): "ports": {"hass": "hass"}, }, } - with patch.object(Device, "async_create_device") as create_device, patch.object( - Device, "async_discover", return_value=mock_coro([]) - ) as async_discover: + async_discover = AsyncMock(return_value=[]) + with patch.object( + Device, "async_create_device", AsyncMock(return_value=mock_device) + ), patch.object(Device, "async_discover", async_discover): + # initialisation of component, no device discovered await async_setup_component(hass, "http", config) await async_setup_component(hass, "upnp", config) await hass.async_block_till_done() - mock_device = MockDevice(udn) - discovery_infos = [ - {"udn": udn, "ssdp_description": "http://192.168.1.1/desc.xml"} - ] - - create_device.return_value = mock_device + # loading of config_entry, device discovered async_discover.return_value = discovery_infos - assert await upnp.async_setup_entry(hass, entry) is True # ensure device is stored/used From f37f488cc3bd3a6e2ef226069831485e08d75a6f Mon Sep 17 00:00:00 2001 From: pdcemulator <20071350+pdcemulator@users.noreply.github.com> Date: Sun, 3 May 2020 04:12:54 +0200 Subject: [PATCH 229/511] Add support for influxdb path parameter (#35089) --- homeassistant/components/influxdb/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index 922a0197cf1..0d1999e0d7b 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -17,6 +17,7 @@ from homeassistant.const import ( CONF_HOST, CONF_INCLUDE, CONF_PASSWORD, + CONF_PATH, CONF_PORT, CONF_SSL, CONF_USERNAME, @@ -83,6 +84,7 @@ CONFIG_SCHEMA = vol.Schema( } ), vol.Optional(CONF_DB_NAME, default=DEFAULT_DATABASE): cv.string, + vol.Optional(CONF_PATH): cv.string, vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL): cv.boolean, vol.Optional(CONF_RETRY_COUNT, default=0): cv.positive_int, @@ -131,6 +133,9 @@ def setup(hass, config): if CONF_HOST in conf: kwargs["host"] = conf[CONF_HOST] + if CONF_PATH in conf: + kwargs["path"] = conf[CONF_PATH] + if CONF_PORT in conf: kwargs["port"] = conf[CONF_PORT] From 661656e37362e26551c86bff2ec499b5d074ff89 Mon Sep 17 00:00:00 2001 From: Quentame Date: Sun, 3 May 2020 07:46:55 +0200 Subject: [PATCH 230/511] Fix Synology NAS discovered multiple times (#35094) --- .../components/synology_dsm/__init__.py | 20 +++++++--- .../components/synology_dsm/config_flow.py | 34 ++++++++--------- .../synology_dsm/test_config_flow.py | 37 ++++++++++++++----- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 3fbed6955d9..9431cb7b1c9 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -11,6 +11,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_DISKS, CONF_HOST, + CONF_MAC, CONF_PASSWORD, CONF_PORT, CONF_SSL, @@ -77,6 +78,13 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.unique_id] = api + # For SSDP compat + if not entry.data.get(CONF_MAC): + network = await hass.async_add_executor_job(getattr, api.dsm, "network") + hass.config_entries.async_update_entry( + entry, data={**entry.data, CONF_MAC: network.macs} + ) + hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "sensor") ) @@ -115,7 +123,7 @@ class SynoApi: self._device_token = device_token self.temp_unit = temp_unit - self._dsm: SynologyDSM = None + self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None self.utilisation: SynoCoreUtilization = None self.storage: SynoStorage = None @@ -129,7 +137,7 @@ class SynoApi: async def async_setup(self): """Start interacting with the NAS.""" - self._dsm = SynologyDSM( + self.dsm = SynologyDSM( self._host, self._port, self._username, @@ -147,9 +155,9 @@ class SynoApi: def _fetch_device_configuration(self): """Fetch initial device config.""" - self.information = self._dsm.information - self.utilisation = self._dsm.utilisation - self.storage = self._dsm.storage + self.information = self.dsm.information + self.utilisation = self.dsm.utilisation + self.storage = self.dsm.storage async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" @@ -157,5 +165,5 @@ class SynoApi: async def update(self, now=None): """Update function for updating API information.""" - await self._hass.async_add_executor_job(self._dsm.update) + await self._hass.async_add_executor_job(self.dsm.update) async_dispatcher_send(self._hass, self.signal_sensor_update) diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 4b09e516451..c3d15aff2fd 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -17,6 +17,7 @@ from homeassistant.components import ssdp from homeassistant.const import ( CONF_DISKS, CONF_HOST, + CONF_MAC, CONF_NAME, CONF_PASSWORD, CONF_PORT, @@ -145,6 +146,7 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_SSL: use_ssl, CONF_USERNAME: username, CONF_PASSWORD: password, + CONF_MAC: api.network.macs, } if otp_code: config_data["device_token"] = api.device_token @@ -162,16 +164,11 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): discovery_info[ssdp.ATTR_UPNP_FRIENDLY_NAME].split("(", 1)[0].strip() ) - if self._host_already_configured(parsed_url.hostname): + # Synology NAS can broadcast on multiple IP addresses, since they can be connected to multiple ethernets. + # The serial of the NAS is actually its MAC address. + if self._mac_already_configured(discovery_info[ssdp.ATTR_UPNP_SERIAL].upper()): return self.async_abort(reason="already_configured") - if ssdp.ATTR_UPNP_SERIAL in discovery_info: - # Synology can broadcast on multiple IP addresses - await self.async_set_unique_id( - discovery_info[ssdp.ATTR_UPNP_SERIAL].upper() - ) - self._abort_if_unique_id_configured() - self.discovered_conf = { CONF_NAME: friendly_name, CONF_HOST: parsed_url.hostname, @@ -205,12 +202,14 @@ class SynologyDSMFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_user(user_input) - def _host_already_configured(self, hostname): - """See if we already have a host matching user input configured.""" - existing_hosts = { - entry.data[CONF_HOST] for entry in self._async_current_entries() - } - return hostname in existing_hosts + def _mac_already_configured(self, mac): + """See if we already have configured a NAS with this MAC address.""" + existing_macs = [ + mac.replace("-", "") + for entry in self._async_current_entries() + for mac in entry.data.get(CONF_MAC, []) + ] + return mac in existing_macs def _login_and_fetch_syno_info(api, otp_code): @@ -221,10 +220,11 @@ def _login_and_fetch_syno_info(api, otp_code): storage = api.storage if ( - api.information.serial is None + not api.information.serial or utilisation.cpu_user_load is None - or storage.disks_ids is None - or storage.volumes_ids is None + or not storage.disks_ids + or not storage.volumes_ids + or not api.network.macs ): raise InvalidData diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 66f752ffaf4..795348d900c 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -25,6 +25,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP, SOURCE_USER from homeassistant.const import ( CONF_DISKS, CONF_HOST, + CONF_MAC, CONF_PASSWORD, CONF_PORT, CONF_SSL, @@ -47,6 +48,8 @@ USERNAME = "Home_Assistant" PASSWORD = "password" DEVICE_TOKEN = "Dév!cè_T0k€ñ" +MACS = ["00-11-32-XX-XX-59", "00-11-32-XX-XX-5A"] + @pytest.fixture(name="service") def mock_controller_service(): @@ -56,8 +59,9 @@ def mock_controller_service(): ) as service_mock: service_mock.return_value.information.serial = SERIAL service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = [] - service_mock.return_value.storage.volumes_ids = [] + service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] + service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.network.macs = MACS yield service_mock @@ -72,8 +76,9 @@ def mock_controller_service_2sa(): ) service_mock.return_value.information.serial = SERIAL service_mock.return_value.utilisation.cpu_user_load = 1 - service_mock.return_value.storage.disks_ids = [] - service_mock.return_value.storage.volumes_ids = [] + service_mock.return_value.storage.disks_ids = ["sda", "sdb", "sdc"] + service_mock.return_value.storage.volumes_ids = ["volume_1"] + service_mock.return_value.network.macs = MACS yield service_mock @@ -85,8 +90,9 @@ def mock_controller_service_failed(): ) as service_mock: service_mock.return_value.information.serial = None service_mock.return_value.utilisation.cpu_user_load = None - service_mock.return_value.storage.disks_ids = None - service_mock.return_value.storage.volumes_ids = None + service_mock.return_value.storage.disks_ids = [] + service_mock.return_value.storage.volumes_ids = [] + service_mock.return_value.network.macs = [] yield service_mock @@ -118,6 +124,7 @@ async def test_user(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -142,6 +149,7 @@ async def test_user(hass: HomeAssistantType, service: MagicMock): assert not result["data"][CONF_SSL] assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -183,6 +191,7 @@ async def test_user_2sa(hass: HomeAssistantType, service_2sa: MagicMock): assert result["data"][CONF_SSL] == DEFAULT_SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") == DEVICE_TOKEN assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -204,6 +213,7 @@ async def test_import(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == DEFAULT_SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None @@ -231,6 +241,7 @@ async def test_import(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") is None assert result["data"][CONF_DISKS] == ["sda", "sdb", "sdc"] assert result["data"][CONF_VOLUMES] == ["volume_1"] @@ -329,8 +340,13 @@ async def test_form_ssdp_already_configured( MockConfigEntry( domain=DOMAIN, - data={CONF_HOST: HOST, CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD}, - unique_id=SERIAL.upper(), + data={ + CONF_HOST: HOST, + CONF_USERNAME: USERNAME, + CONF_PASSWORD: PASSWORD, + CONF_MAC: MACS, + }, + unique_id=SERIAL, ).add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -339,7 +355,7 @@ async def test_form_ssdp_already_configured( data={ ssdp.ATTR_SSDP_LOCATION: "http://192.168.1.5:5000", ssdp.ATTR_UPNP_FRIENDLY_NAME: "mydsm", - ssdp.ATTR_UPNP_SERIAL: SERIAL, + ssdp.ATTR_UPNP_SERIAL: "001132XXXX59", # Existing in MACS[0], but SSDP does not have `-` }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -355,7 +371,7 @@ async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock): data={ ssdp.ATTR_SSDP_LOCATION: "http://192.168.1.5:5000", ssdp.ATTR_UPNP_FRIENDLY_NAME: "mydsm", - ssdp.ATTR_UPNP_SERIAL: SERIAL, + ssdp.ATTR_UPNP_SERIAL: "001132XXXX99", # MAC address, but SSDP does not have `-` }, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -373,6 +389,7 @@ async def test_form_ssdp(hass: HomeAssistantType, service: MagicMock): assert result["data"][CONF_SSL] == DEFAULT_SSL assert result["data"][CONF_USERNAME] == USERNAME assert result["data"][CONF_PASSWORD] == PASSWORD + assert result["data"][CONF_MAC] == MACS assert result["data"].get("device_token") is None assert result["data"].get(CONF_DISKS) is None assert result["data"].get(CONF_VOLUMES) is None From de7f82e18e82b4ee9b260f89e9ea2b5c1736ba20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sun, 3 May 2020 11:36:06 +0200 Subject: [PATCH 231/511] Correct typo Asssitant -> Assistant (#35117) --- homeassistant/components/websocket_api/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/websocket_api/manifest.json b/homeassistant/components/websocket_api/manifest.json index 76e2742b996..66dd76af769 100644 --- a/homeassistant/components/websocket_api/manifest.json +++ b/homeassistant/components/websocket_api/manifest.json @@ -1,6 +1,6 @@ { "domain": "websocket_api", - "name": "Home Asssitant WebSocket API", + "name": "Home Assistant WebSocket API", "documentation": "https://www.home-assistant.io/integrations/websocket_api", "dependencies": ["http"], "codeowners": ["@home-assistant/core"], From a44724fdcc829a7185884f2415cf45ee9f7579b4 Mon Sep 17 00:00:00 2001 From: On Freund Date: Sun, 3 May 2020 14:45:39 +0300 Subject: [PATCH 232/511] Add codeowner to Monoprice integration (#35111) --- CODEOWNERS | 2 +- homeassistant/components/monoprice/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index a537e562525..7d112e919a7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -240,7 +240,7 @@ homeassistant/components/minecraft_server/* @elmurato homeassistant/components/minio/* @tkislan homeassistant/components/mobile_app/* @robbiet480 homeassistant/components/modbus/* @adamchengtkc @janiversen -homeassistant/components/monoprice/* @etsinko +homeassistant/components/monoprice/* @etsinko @OnFreund homeassistant/components/moon/* @fabaff homeassistant/components/mpd/* @fabaff homeassistant/components/mqtt/* @home-assistant/core @emontnemery diff --git a/homeassistant/components/monoprice/manifest.json b/homeassistant/components/monoprice/manifest.json index c88673b2855..93cebc9d885 100644 --- a/homeassistant/components/monoprice/manifest.json +++ b/homeassistant/components/monoprice/manifest.json @@ -3,6 +3,6 @@ "name": "Monoprice 6-Zone Amplifier", "documentation": "https://www.home-assistant.io/integrations/monoprice", "requirements": ["pymonoprice==0.3"], - "codeowners": ["@etsinko"], + "codeowners": ["@etsinko", "@OnFreund"], "config_flow": true } From e0bcd0049cb4d7741d78835656e1c12daa9eef7e Mon Sep 17 00:00:00 2001 From: On Freund Date: Sun, 3 May 2020 14:49:18 +0300 Subject: [PATCH 233/511] Fix unloading of Monoprice config entries (#35112) --- homeassistant/components/monoprice/__init__.py | 14 +++++++++++--- homeassistant/components/monoprice/const.py | 3 +++ homeassistant/components/monoprice/media_player.py | 10 ++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/monoprice/__init__.py b/homeassistant/components/monoprice/__init__.py index 37593f6828e..d5173d40d89 100644 --- a/homeassistant/components/monoprice/__init__.py +++ b/homeassistant/components/monoprice/__init__.py @@ -10,7 +10,7 @@ from homeassistant.const import CONF_PORT from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from .const import DOMAIN +from .const import DOMAIN, MONOPRICE_OBJECT, UNDO_UPDATE_LISTENER PLATFORMS = ["media_player"] @@ -28,12 +28,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: monoprice = await hass.async_add_executor_job(get_monoprice, port) - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = monoprice except SerialException: _LOGGER.error("Error connecting to Monoprice controller at %s", port) raise ConfigEntryNotReady - entry.add_update_listener(_update_listener) + undo_listener = entry.add_update_listener(_update_listener) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + MONOPRICE_OBJECT: monoprice, + UNDO_UPDATE_LISTENER: undo_listener, + } for component in PLATFORMS: hass.async_create_task( @@ -54,6 +58,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) + if unload_ok: + hass.data[DOMAIN][entry.entry_id][UNDO_UPDATE_LISTENER]() + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/monoprice/const.py b/homeassistant/components/monoprice/const.py index ea4667a77ff..180ec452831 100644 --- a/homeassistant/components/monoprice/const.py +++ b/homeassistant/components/monoprice/const.py @@ -13,3 +13,6 @@ CONF_SOURCE_6 = "source_6" SERVICE_SNAPSHOT = "snapshot" SERVICE_RESTORE = "restore" + +MONOPRICE_OBJECT = "monoprice_object" +UNDO_UPDATE_LISTENER = "update_update_listener" diff --git a/homeassistant/components/monoprice/media_player.py b/homeassistant/components/monoprice/media_player.py index 855d1b41f98..985cd88e47f 100644 --- a/homeassistant/components/monoprice/media_player.py +++ b/homeassistant/components/monoprice/media_player.py @@ -16,7 +16,13 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import CONF_PORT, STATE_OFF, STATE_ON from homeassistant.helpers import config_validation as cv, entity_platform, service -from .const import CONF_SOURCES, DOMAIN, SERVICE_RESTORE, SERVICE_SNAPSHOT +from .const import ( + CONF_SOURCES, + DOMAIN, + MONOPRICE_OBJECT, + SERVICE_RESTORE, + SERVICE_SNAPSHOT, +) _LOGGER = logging.getLogger(__name__) @@ -58,7 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Monoprice 6-zone amplifier platform.""" port = config_entry.data[CONF_PORT] - monoprice = hass.data[DOMAIN][config_entry.entry_id] + monoprice = hass.data[DOMAIN][config_entry.entry_id][MONOPRICE_OBJECT] sources = _get_sources(config_entry) From d644b610b7fb964a84988aad5519bfc0aa38b086 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 3 May 2020 13:58:59 +0200 Subject: [PATCH 234/511] Increase surepetcare default timeout (#34944) * add timeout parameter and increase default timeout * remove timeout param * revert BinarySensorDevice to BinarySensorEntity * make isort happy * make isort happy - again --- homeassistant/components/surepetcare/__init__.py | 2 ++ homeassistant/components/surepetcare/binary_sensor.py | 4 ++-- homeassistant/components/surepetcare/const.py | 3 +++ homeassistant/components/surepetcare/manifest.json | 2 +- requirements_all.txt | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index a22ba4a1335..90e754118ab 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -32,6 +32,7 @@ from .const import ( DEFAULT_SCAN_INTERVAL, DOMAIN, SPC, + SURE_API_TIMEOUT, TOPIC_UPDATE, ) @@ -78,6 +79,7 @@ async def async_setup(hass, config) -> bool: conf[CONF_PASSWORD], hass.loop, async_get_clientsession(hass), + api_timeout=SURE_API_TIMEOUT, ) await surepy.get_data() except SurePetcareAuthenticationError: diff --git a/homeassistant/components/surepetcare/binary_sensor.py b/homeassistant/components/surepetcare/binary_sensor.py index 26f498d43fe..efd5048053f 100644 --- a/homeassistant/components/surepetcare/binary_sensor.py +++ b/homeassistant/components/surepetcare/binary_sensor.py @@ -105,7 +105,7 @@ class SurePetcareBinarySensor(BinarySensorEntity): return None if not self._device_class else self._device_class @property - def unique_id(self: BinarySensorEntity) -> str: + def unique_id(self) -> str: """Return an unique ID.""" return f"{self._spc_data['household_id']}-{self._id}" @@ -214,7 +214,7 @@ class DeviceConnectivity(SurePetcareBinarySensor): return f"{self._name}_connectivity" @property - def unique_id(self: BinarySensorEntity) -> str: + def unique_id(self) -> str: """Return an unique ID.""" return f"{self._spc_data['household_id']}-{self._id}-connectivity" diff --git a/homeassistant/components/surepetcare/const.py b/homeassistant/components/surepetcare/const.py index d534398784f..7f0213be4ef 100644 --- a/homeassistant/components/surepetcare/const.py +++ b/homeassistant/components/surepetcare/const.py @@ -23,6 +23,9 @@ SURE_IDS = "sure_ids" # platforms TOPIC_UPDATE = f"{DOMAIN}_data_update" +# sure petcare api +SURE_API_TIMEOUT = 15 + # flap BATTERY_ICON = "mdi:battery" SURE_BATT_VOLTAGE_FULL = 1.6 # voltage diff --git a/homeassistant/components/surepetcare/manifest.json b/homeassistant/components/surepetcare/manifest.json index 6d34ff477ce..659a6091299 100644 --- a/homeassistant/components/surepetcare/manifest.json +++ b/homeassistant/components/surepetcare/manifest.json @@ -3,5 +3,5 @@ "name": "Sure Petcare", "documentation": "https://www.home-assistant.io/integrations/surepetcare", "codeowners": ["@benleb"], - "requirements": ["surepy==0.2.3"] + "requirements": ["surepy==0.2.5"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6b896f0bd85..bbdb9165bc7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2005,7 +2005,7 @@ sucks==0.9.4 sunwatcher==0.2.1 # homeassistant.components.surepetcare -surepy==0.2.3 +surepy==0.2.5 # homeassistant.components.swiss_hydrological_data swisshydrodata==0.0.3 From 5a2528b0f150143dcc646b1687580f2d4c795337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sun, 3 May 2020 14:40:19 +0200 Subject: [PATCH 235/511] Tibber config flow (#34469) * tibber config, wip * read config from yaml * sync requirements * style * add model property * unique id * unique id * Tibber config, unique id * test doc * tibber config, update title * append _el_price * Update homeassistant/components/tibber/__init__.py Co-authored-by: Paulus Schoutsen * unique id * tibber config flow * tibber config flow * fix test for python 3.8 * update test imports * move _async_current_entries Co-authored-by: Paulus Schoutsen --- homeassistant/components/tibber/__init__.py | 74 ++++++++++++++----- .../components/tibber/config_flow.py | 68 +++++++++++++++++ homeassistant/components/tibber/const.py | 5 ++ homeassistant/components/tibber/manifest.json | 5 +- homeassistant/components/tibber/sensor.py | 48 +++++++++--- homeassistant/components/tibber/strings.json | 22 ++++++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 2 +- requirements_test_all.txt | 3 + tests/components/tibber/__init__.py | 1 + tests/components/tibber/test_config_flow.py | 69 +++++++++++++++++ 11 files changed, 268 insertions(+), 30 deletions(-) create mode 100644 homeassistant/components/tibber/config_flow.py create mode 100644 homeassistant/components/tibber/const.py create mode 100644 homeassistant/components/tibber/strings.json create mode 100644 tests/components/tibber/__init__.py create mode 100644 tests/components/tibber/test_config_flow.py diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py index 53c02a1461a..657be67c7fc 100644 --- a/homeassistant/components/tibber/__init__.py +++ b/homeassistant/components/tibber/__init__.py @@ -6,16 +6,19 @@ import aiohttp import tibber import voluptuous as vol +from homeassistant import config_entries from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, EVENT_HOMEASSISTANT_STOP +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_call_later from homeassistant.util import dt as dt_util -DOMAIN = "tibber" +from .const import DATA_HASS_CONFIG, DOMAIN -FIRST_RETRY_TIME = 60 +PLATFORMS = [ + "sensor", +] CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})}, @@ -25,12 +28,30 @@ CONFIG_SCHEMA = vol.Schema( _LOGGER = logging.getLogger(__name__) -async def async_setup(hass, config, retry_delay=FIRST_RETRY_TIME): +async def async_setup(hass, config): """Set up the Tibber component.""" - conf = config.get(DOMAIN) + + hass.data[DATA_HASS_CONFIG] = config + + if DOMAIN not in config: + return True + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=config[DOMAIN], + ) + ) + + return True + + +async def async_setup_entry(hass, entry): + """Set up a config entry.""" tibber_connection = tibber.Tibber( - conf[CONF_ACCESS_TOKEN], + access_token=entry.data[CONF_ACCESS_TOKEN], websession=async_get_clientsession(hass), time_zone=dt_util.DEFAULT_TIME_ZONE, ) @@ -44,15 +65,7 @@ async def async_setup(hass, config, retry_delay=FIRST_RETRY_TIME): try: await tibber_connection.update_info() except asyncio.TimeoutError: - _LOGGER.warning("Timeout connecting to Tibber. Will retry in %ss", retry_delay) - - async def retry_setup(now): - """Retry setup if a timeout happens on Tibber API.""" - await async_setup(hass, config, retry_delay=min(2 * retry_delay, 900)) - - async_call_later(hass, retry_delay, retry_setup) - - return True + raise ConfigEntryNotReady except aiohttp.ClientError as err: _LOGGER.error("Error connecting to Tibber: %s ", err) return False @@ -60,7 +73,34 @@ async def async_setup(hass, config, retry_delay=FIRST_RETRY_TIME): _LOGGER.error("Failed to login. %s", exp) return False - for component in ["sensor", "notify"]: - discovery.load_platform(hass, component, DOMAIN, {CONF_NAME: DOMAIN}, config) + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + # set up notify platform, no entry support for notify component yet, + # have to use discovery to load platform. + hass.async_create_task( + discovery.async_load_platform( + hass, "notify", DOMAIN, {CONF_NAME: DOMAIN}, hass.data[DATA_HASS_CONFIG] + ) + ) return True + + +async def async_unload_entry(hass, config_entry): + """Unload a 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: + tibber_connection = hass.data.get(DOMAIN) + await tibber_connection.rt_disconnect() + + return unload_ok diff --git a/homeassistant/components/tibber/config_flow.py b/homeassistant/components/tibber/config_flow.py new file mode 100644 index 00000000000..b0115d84e2c --- /dev/null +++ b/homeassistant/components/tibber/config_flow.py @@ -0,0 +1,68 @@ +"""Adds config flow for Tibber integration.""" +import asyncio +import logging + +import aiohttp +import tibber +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}) + + +class TibberConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Tibber integration.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_import(self, import_info): + """Set the config entry up from yaml.""" + return await self.async_step_user(import_info) + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + + if self._async_current_entries(): + return self.async_abort(reason="already_configured") + + if user_input is not None: + access_token = user_input[CONF_ACCESS_TOKEN].replace(" ", "") + + tibber_connection = tibber.Tibber( + access_token=access_token, + websession=async_get_clientsession(self.hass), + ) + + errors = {} + + try: + await tibber_connection.update_info() + except asyncio.TimeoutError: + errors[CONF_ACCESS_TOKEN] = "timeout" + except aiohttp.ClientError: + errors[CONF_ACCESS_TOKEN] = "connection_error" + except tibber.InvalidLogin: + errors[CONF_ACCESS_TOKEN] = "invalid_access_token" + + if errors: + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors, + ) + + unique_id = tibber_connection.user_id + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=tibber_connection.name, data={CONF_ACCESS_TOKEN: access_token}, + ) + + return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA, errors={},) diff --git a/homeassistant/components/tibber/const.py b/homeassistant/components/tibber/const.py new file mode 100644 index 00000000000..a35fa89c40f --- /dev/null +++ b/homeassistant/components/tibber/const.py @@ -0,0 +1,5 @@ +"""Constants for Tibber integration.""" + +DATA_HASS_CONFIG = "tibber_hass_config" +DOMAIN = "tibber" +MANUFACTURER = "Tibber" diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index 78249a96291..36f4002949b 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -2,7 +2,8 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.13.8"], + "requirements": ["pyTibber==0.14.0"], "codeowners": ["@danielhiversen"], - "quality_scale": "silver" + "quality_scale": "silver", + "config_flow": true } diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 36f1a65222c..7fc8820e92d 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -10,7 +10,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle, dt as dt_util -from . import DOMAIN as TIBBER_DOMAIN +from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) @@ -20,10 +20,8 @@ SCAN_INTERVAL = timedelta(minutes=1) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +async def async_setup_entry(hass, entry, async_add_entities): """Set up the Tibber sensor.""" - if discovery_info is None: - return tibber_connection = hass.data.get(TIBBER_DOMAIN) @@ -66,11 +64,34 @@ class TibberSensor(Entity): """Return the state attributes.""" return self._device_state_attributes + @property + def model(self): + """Return the model of the sensor.""" + return None + @property def state(self): """Return the state of the device.""" return self._state + @property + def device_id(self): + """Return the ID of the physical device this sensor is part of.""" + home = self._tibber_home.info["viewer"]["home"] + return home["meteringPointData"]["consumptionEan"] + + @property + def device_info(self): + """Return the device_info of the device.""" + device_info = { + "identifiers": {(TIBBER_DOMAIN, self.device_id)}, + "name": self.name, + "manufacturer": MANUFACTURER, + } + if self.model is not None: + device_info["model"] = self.model + return device_info + class TibberSensorElPrice(TibberSensor): """Representation of a Tibber sensor for el price.""" @@ -112,6 +133,11 @@ class TibberSensorElPrice(TibberSensor): """Return the name of the sensor.""" return f"Electricity price {self._name}" + @property + def model(self): + """Return the model of the sensor.""" + return "Price Sensor" + @property def icon(self): """Return the icon to use in the frontend.""" @@ -125,8 +151,7 @@ class TibberSensorElPrice(TibberSensor): @property def unique_id(self): """Return a unique ID.""" - home = self._tibber_home.info["viewer"]["home"] - return home["meteringPointData"]["consumptionEan"] + return self.device_id @Throttle(MIN_TIME_BETWEEN_UPDATES) async def _fetch_data(self): @@ -149,7 +174,7 @@ class TibberSensorRT(TibberSensor): """Representation of a Tibber sensor for real time consumption.""" async def async_added_to_hass(self): - """Start unavailability tracking.""" + """Start listen for real time data.""" await self._tibber_home.rt_subscribe(self.hass.loop, self._async_callback) async def _async_callback(self, payload): @@ -177,6 +202,11 @@ class TibberSensorRT(TibberSensor): """Return True if entity is available.""" return self._tibber_home.rt_subscription_running + @property + def model(self): + """Return the model of the sensor.""" + return "Tibber Pulse" + @property def name(self): """Return the name of the sensor.""" @@ -200,6 +230,4 @@ class TibberSensorRT(TibberSensor): @property def unique_id(self): """Return a unique ID.""" - home = self._tibber_home.info["viewer"]["home"] - _id = home["meteringPointData"]["consumptionEan"] - return f"{_id}_rt_consumption" + return f"{self.device_id}_rt_consumption" diff --git a/homeassistant/components/tibber/strings.json b/homeassistant/components/tibber/strings.json new file mode 100644 index 00000000000..a856c96e2f9 --- /dev/null +++ b/homeassistant/components/tibber/strings.json @@ -0,0 +1,22 @@ +{ + "title": "Tibber", + "config": { + "abort": { + "already_configured": "A Tibber account is already configured." + }, + "error": { + "timeout": "Timeout connecting to Tibber", + "connection_error": "Error connecting to Tibber", + "invalid_access_token": "Invalid access token" + }, + "step": { + "user": { + "data": { + "access_token": "Access token" + }, + "description": "Enter your access token from https://developer.tibber.com/settings/accesstoken", + "title": "Tibber" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 0fde3dc9676..472f722538d 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -125,6 +125,7 @@ FLOWS = [ "tado", "tellduslive", "tesla", + "tibber", "toon", "totalconnect", "tplink", diff --git a/requirements_all.txt b/requirements_all.txt index bbdb9165bc7..0a50553bdd2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1158,7 +1158,7 @@ pyRFXtrx==0.25 # pySwitchmate==0.4.6 # homeassistant.components.tibber -pyTibber==0.13.8 +pyTibber==0.14.0 # homeassistant.components.dlink pyW215==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bfeaad74448..02dd55bb2d2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -475,6 +475,9 @@ pyMetno==0.4.6 # homeassistant.components.rfxtrx pyRFXtrx==0.25 +# homeassistant.components.tibber +pyTibber==0.14.0 + # homeassistant.components.nextbus py_nextbusnext==0.1.4 diff --git a/tests/components/tibber/__init__.py b/tests/components/tibber/__init__.py new file mode 100644 index 00000000000..0633a3da06e --- /dev/null +++ b/tests/components/tibber/__init__.py @@ -0,0 +1 @@ +"""Tests for Tibber.""" diff --git a/tests/components/tibber/test_config_flow.py b/tests/components/tibber/test_config_flow.py new file mode 100644 index 00000000000..6b2428fcff9 --- /dev/null +++ b/tests/components/tibber/test_config_flow.py @@ -0,0 +1,69 @@ +"""Tests for Tibber config flow.""" +import pytest + +from homeassistant.components.tibber.const import DOMAIN +from homeassistant.const import CONF_ACCESS_TOKEN + +from tests.async_mock import AsyncMock, MagicMock, PropertyMock, patch +from tests.common import MockConfigEntry + + +@pytest.fixture(name="tibber_setup", autouse=True) +def tibber_setup_fixture(): + """Patch tibber setup entry.""" + with patch("homeassistant.components.tibber.async_setup_entry", return_value=True): + yield + + +async def test_show_config_form(hass): + """Test show configuration form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + + assert result["type"] == "form" + assert result["step_id"] == "user" + + +async def test_create_entry(hass): + """Test create entry from user input.""" + test_data = { + CONF_ACCESS_TOKEN: "valid", + } + + unique_user_id = "unique_user_id" + title = "title" + + tibber_mock = MagicMock() + type(tibber_mock).update_info = AsyncMock(return_value=True) + type(tibber_mock).user_id = PropertyMock(return_value=unique_user_id) + type(tibber_mock).name = PropertyMock(return_value=title) + + with patch("tibber.Tibber", return_value=tibber_mock): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=test_data + ) + + assert result["type"] == "create_entry" + assert result["title"] == title + assert result["data"] == test_data + + +async def test_flow_entry_already_exists(hass): + """Test user input for config_entry that already exists.""" + first_entry = MockConfigEntry( + domain="tibber", data={CONF_ACCESS_TOKEN: "valid"}, unique_id="tibber", + ) + first_entry.add_to_hass(hass) + + test_data = { + CONF_ACCESS_TOKEN: "valid", + } + + with patch("tibber.Tibber.update_info", return_value=None): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"}, data=test_data + ) + + assert result["type"] == "abort" + assert result["reason"] == "already_configured" From 90fbb150e75f564045fb598c13bc23cb92e9f0da Mon Sep 17 00:00:00 2001 From: On Freund Date: Sun, 3 May 2020 16:18:25 +0300 Subject: [PATCH 236/511] Use suggestd_value instead of default in Monoprice options flow (#35107) --- homeassistant/components/monoprice/config_flow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/monoprice/config_flow.py b/homeassistant/components/monoprice/config_flow.py index cbabc65a54b..6c6bc87bf28 100644 --- a/homeassistant/components/monoprice/config_flow.py +++ b/homeassistant/components/monoprice/config_flow.py @@ -99,7 +99,9 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @core.callback def _key_for_source(index, source, previous_sources): if str(index) in previous_sources: - key = vol.Optional(source, default=previous_sources[str(index)]) + key = vol.Optional( + source, description={"suggested_value": previous_sources[str(index)]} + ) else: key = vol.Optional(source) From e7fc886992f266087dc67805738e9a880dfe2350 Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Sun, 3 May 2020 18:48:24 +0200 Subject: [PATCH 237/511] Simplify MQTT light brightness logic (#35097) --- homeassistant/components/mqtt/light/schema_json.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index ef311cbe8a7..b803621fbb8 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -428,9 +428,7 @@ class MqttLightJson( if self._brightness is not None: brightness = 255 else: - brightness = kwargs.get( - ATTR_BRIGHTNESS, self._brightness if self._brightness else 255 - ) + brightness = kwargs.get(ATTR_BRIGHTNESS, 255) rgb = color_util.color_hsv_to_RGB( hs_color[0], hs_color[1], brightness / 255 * 100 ) From 2af984917e7cf3ee649ed3a531a872e7aaeba713 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 3 May 2020 11:27:19 -0700 Subject: [PATCH 238/511] Use asynctest-mock in most places (#35109) * Use asynctest-mock in most places * Fix broken patch in pilight --- tests/auth/mfa_modules/test_notify.py | 2 +- tests/auth/mfa_modules/test_totp.py | 2 +- tests/auth/test_init.py | 2 +- tests/components/abode/common.py | 3 +-- .../abode/test_alarm_control_panel.py | 4 ++-- tests/components/abode/test_camera.py | 4 ++-- tests/components/abode/test_config_flow.py | 3 +-- tests/components/abode/test_cover.py | 4 ++-- tests/components/abode/test_init.py | 4 ++-- tests/components/abode/test_light.py | 4 ++-- tests/components/abode/test_lock.py | 4 ++-- tests/components/abode/test_switch.py | 4 ++-- tests/components/androidtv/patchers.py | 2 +- .../components/androidtv/test_media_player.py | 3 ++- tests/components/api/test_init.py | 2 +- tests/components/apns/test_notify.py | 2 +- tests/components/apprise/test_notify.py | 4 ++-- tests/components/aprs/test_device_tracker.py | 3 +-- tests/components/arlo/test_sensor.py | 5 ++-- tests/components/atag/test_config_flow.py | 4 +--- tests/components/august/test_gateway.py | 4 +--- tests/components/auth/test_init.py | 2 +- tests/components/auth/test_login_flow.py | 3 +-- .../automation/test_homeassistant.py | 4 +--- tests/components/automation/test_init.py | 2 +- .../automation/test_numeric_state.py | 2 +- tests/components/automation/test_state.py | 2 +- tests/components/automation/test_sun.py | 2 +- tests/components/automation/test_time.py | 2 +- tests/components/axis/test_init.py | 4 +--- tests/components/axis/test_switch.py | 4 ++-- .../binary_sensor/test_device_condition.py | 2 +- .../test_device_tracker.py | 2 +- tests/components/bom/test_sensor.py | 2 +- tests/components/broadlink/test_init.py | 3 ++- tests/components/caldav/test_calendar.py | 3 +-- tests/components/camera/test_init.py | 3 +-- tests/components/canary/test_init.py | 2 +- tests/components/canary/test_sensor.py | 2 +- .../cast/test_home_assistant_cast.py | 3 +-- tests/components/climate/test_init.py | 2 +- tests/components/cloud/conftest.py | 4 ++-- tests/components/cloud/test_binary_sensor.py | 4 +--- tests/components/cloud/test_google_config.py | 4 +--- tests/components/cloud/test_prefs.py | 4 ++-- tests/components/coinmarketcap/test_sensor.py | 2 +- tests/components/command_line/test_notify.py | 2 +- tests/components/command_line/test_sensor.py | 2 +- tests/components/config/test_group.py | 3 +-- tests/components/config/test_script.py | 4 ++-- tests/components/config/test_zwave.py | 2 +- tests/components/daikin/test_config_flow.py | 2 +- tests/components/darksky/test_sensor.py | 2 +- tests/components/darksky/test_weather.py | 2 +- tests/components/default_config/test_init.py | 4 ++-- tests/components/demo/test_camera.py | 4 ++-- tests/components/demo/test_geo_location.py | 2 +- tests/components/demo/test_notify.py | 2 +- .../components/denonavr/test_media_player.py | 4 ++-- tests/components/derivative/test_sensor.py | 3 ++- tests/components/dialogflow/test_init.py | 3 ++- tests/components/dsmr/test_sensor.py | 2 +- tests/components/emulated_hue/test_hue_api.py | 2 +- tests/components/emulated_hue/test_init.py | 4 ++-- tests/components/emulated_hue/test_upnp.py | 2 +- .../components/emulated_roku/test_binding.py | 4 +--- tests/components/emulated_roku/test_init.py | 4 +--- .../facebox/test_image_processing.py | 4 ++-- tests/components/fail2ban/test_sensor.py | 2 +- tests/components/feedreader/test_init.py | 2 +- tests/components/ffmpeg/test_init.py | 3 +-- tests/components/fido/test_sensor.py | 2 +- tests/components/file/test_notify.py | 2 +- tests/components/file/test_sensor.py | 2 +- tests/components/filter/test_sensor.py | 2 +- tests/components/folder_watcher/test_init.py | 3 ++- tests/components/foobot/test_sensor.py | 2 +- tests/components/freebox/conftest.py | 4 ++-- tests/components/fritzbox/__init__.py | 4 ++-- tests/components/fritzbox/conftest.py | 4 ++-- .../components/fritzbox/test_binary_sensor.py | 2 +- tests/components/fritzbox/test_climate.py | 2 +- tests/components/fritzbox/test_config_flow.py | 3 ++- tests/components/fritzbox/test_init.py | 3 +-- tests/components/fritzbox/test_sensor.py | 2 +- tests/components/fritzbox/test_switch.py | 2 +- .../garmin_connect/test_config_flow.py | 3 +-- tests/components/gdacs/__init__.py | 2 +- .../components/geo_rss_events/test_sensor.py | 2 +- tests/components/geofency/test_init.py | 6 ++--- tests/components/geonetnz_quakes/__init__.py | 2 +- tests/components/geonetnz_volcano/__init__.py | 2 +- tests/components/google/conftest.py | 4 ++-- tests/components/google/test_calendar.py | 2 +- tests/components/google/test_init.py | 4 ++-- .../components/google_assistant/test_trait.py | 3 +-- tests/components/google_translate/test_tts.py | 2 +- tests/components/google_wifi/test_sensor.py | 2 +- tests/components/gpslogger/test_init.py | 4 ++-- tests/components/graphite/test_init.py | 2 +- tests/components/group/test_init.py | 2 +- tests/components/group/test_light.py | 3 +-- tests/components/group/test_notify.py | 2 +- .../components/group/test_reproduce_state.py | 3 ++- tests/components/hangouts/test_config_flow.py | 4 ++-- tests/components/hassio/test_http.py | 3 ++- tests/components/hddtemp/test_sensor.py | 2 +- .../here_travel_time/test_sensor.py | 2 +- tests/components/history/test_init.py | 2 +- tests/components/history_stats/test_sensor.py | 2 +- tests/components/homeassistant/test_scene.py | 3 +-- tests/components/homekit/common.py | 2 +- tests/components/homekit/conftest.py | 4 ++-- tests/components/homekit/test_accessories.py | 2 +- .../homekit/test_get_accessories.py | 4 ++-- tests/components/homekit/test_homekit.py | 3 +-- tests/components/homekit/test_type_fans.py | 2 +- .../homekit/test_type_thermostats.py | 2 +- tests/components/html5/test_notify.py | 3 ++- tests/components/http/test_auth.py | 3 ++- tests/components/http/test_ban.py | 3 +-- tests/components/http/test_cors.py | 3 ++- tests/components/http/test_data_validator.py | 4 ++-- tests/components/http/test_init.py | 3 ++- tests/components/http/test_view.py | 4 +--- tests/components/hue/conftest.py | 3 ++- tests/components/hue/test_config_flow.py | 3 +-- tests/components/hue/test_light.py | 3 ++- tests/components/hue/test_sensor_base.py | 3 ++- tests/components/icloud/conftest.py | 4 ++-- tests/components/icloud/test_config_flow.py | 3 +-- tests/components/ifttt/test_init.py | 4 ++-- .../ign_sismologia/test_geo_location.py | 2 +- .../components/image_processing/test_init.py | 4 +--- tests/components/input_boolean/test_init.py | 2 +- tests/components/input_datetime/test_init.py | 2 +- tests/components/input_number/test_init.py | 5 ++-- tests/components/input_select/test_init.py | 5 ++-- tests/components/input_text/test_init.py | 5 ++-- tests/components/integration/test_sensor.py | 3 ++- .../islamic_prayer_times/test_config_flow.py | 3 +-- .../islamic_prayer_times/test_init.py | 2 +- .../islamic_prayer_times/test_sensor.py | 3 +-- tests/components/jewish_calendar/__init__.py | 3 ++- tests/components/kira/test_init.py | 2 +- tests/components/kira/test_remote.py | 2 +- tests/components/kira/test_sensor.py | 2 +- tests/components/lastfm/test_sensor.py | 4 ++-- .../components/light/test_device_condition.py | 2 +- tests/components/light/test_init.py | 2 +- tests/components/linky/conftest.py | 4 ++-- tests/components/linky/test_config_flow.py | 3 +-- tests/components/locative/test_init.py | 4 ++-- tests/components/lovelace/test_dashboard.py | 3 +-- tests/components/luftdaten/test_init.py | 4 ++-- tests/components/mailgun/test_init.py | 3 ++- .../manual/test_alarm_control_panel.py | 2 +- .../manual_mqtt/test_alarm_control_panel.py | 2 +- tests/components/melissa/test_climate.py | 3 +-- tests/components/meteo_france/conftest.py | 4 ++-- .../meteo_france/test_config_flow.py | 3 +-- tests/components/mfi/test_sensor.py | 2 +- tests/components/mfi/test_switch.py | 2 +- tests/components/mhz19/test_sensor.py | 2 +- tests/components/mikrotik/test_config_flow.py | 2 +- tests/components/minio/test_minio.py | 3 +-- tests/components/mochad/test_light.py | 2 +- tests/components/mochad/test_switch.py | 2 +- tests/components/moon/test_sensor.py | 2 +- tests/components/mqtt/test_binary_sensor.py | 2 +- tests/components/mqtt/test_climate.py | 22 ++++-------------- tests/components/mqtt/test_common.py | 2 +- tests/components/mqtt/test_discovery.py | 3 +-- tests/components/mqtt/test_sensor.py | 2 +- .../components/mqtt_eventstream/test_init.py | 2 +- tests/components/mqtt_room/test_sensor.py | 2 +- .../components/mqtt_statestream/test_init.py | 3 +-- tests/components/neato/test_config_flow.py | 3 +-- tests/components/neato/test_init.py | 3 +-- tests/components/nextbus/test_sensor.py | 2 +- .../nsw_fuel_station/test_sensor.py | 2 +- .../test_geo_location.py | 3 +-- tests/components/nuheat/test_init.py | 4 ++-- tests/components/nws/conftest.py | 4 +--- tests/components/onboarding/test_views.py | 2 +- tests/components/openerz/test_sensor.py | 4 ++-- tests/components/owntracks/test_helper.py | 4 ++-- .../panasonic_viera/test_config_flow.py | 4 +--- tests/components/panasonic_viera/test_init.py | 3 +-- tests/components/panel_custom/test_init.py | 4 ++-- tests/components/pi_hole/test_init.py | 4 +--- tests/components/pilight/test_init.py | 10 ++++---- tests/components/ptvsd/test_ptvsd.py | 4 +--- tests/components/pushbullet/test_notify.py | 2 +- .../pvpc_hourly_pricing/test_config_flow.py | 2 +- .../pvpc_hourly_pricing/test_sensor.py | 2 +- tests/components/python_script/test_init.py | 2 +- .../qld_bushfire/test_geo_location.py | 2 +- tests/components/radarr/test_sensor.py | 19 +++++++-------- tests/components/random/test_binary_sensor.py | 2 +- tests/components/recorder/test_init.py | 2 +- tests/components/recorder/test_migrate.py | 5 ++-- tests/components/recorder/test_purge.py | 2 +- tests/components/recorder/test_util.py | 3 +-- tests/components/reddit/test_sensor.py | 2 +- .../components/remember_the_milk/test_init.py | 2 +- tests/components/rest/test_binary_sensor.py | 2 +- tests/components/rest/test_sensor.py | 2 +- tests/components/rflink/test_binary_sensor.py | 2 +- tests/components/rflink/test_init.py | 4 ++-- tests/components/ring/common.py | 3 +-- tests/components/ring/test_binary_sensor.py | 3 ++- tests/components/script/test_init.py | 2 +- .../signal_messenger/test_notify.py | 3 ++- .../components/simplisafe/test_config_flow.py | 3 +-- .../components/sleepiq/test_binary_sensor.py | 2 +- tests/components/sleepiq/test_init.py | 2 +- tests/components/sleepiq/test_sensor.py | 2 +- tests/components/smhi/common.py | 2 +- tests/components/smhi/test_init.py | 4 ++-- tests/components/smtp/test_notify.py | 2 +- .../components/solaredge/test_config_flow.py | 3 +-- tests/components/soma/test_config_flow.py | 3 +-- tests/components/somfy/test_config_flow.py | 2 +- tests/components/sonarr/test_sensor.py | 23 ++++++++++--------- .../soundtouch/test_media_player.py | 4 +--- tests/components/spotify/test_config_flow.py | 3 +-- tests/components/statistics/test_sensor.py | 2 +- tests/components/stream/test_init.py | 4 +--- tests/components/stream/test_recorder.py | 2 +- tests/components/sun/test_init.py | 3 ++- .../switch/test_device_condition.py | 2 +- tests/components/synology_dsm/conftest.py | 4 ++-- .../synology_dsm/test_config_flow.py | 2 +- tests/components/system_log/test_init.py | 3 ++- tests/components/tcp/test_binary_sensor.py | 2 +- tests/components/tcp/test_sensor.py | 2 +- tests/components/time_date/test_sensor.py | 2 +- tests/components/timer/test_init.py | 2 +- tests/components/tod/test_binary_sensor.py | 2 +- tests/components/toon/test_config_flow.py | 3 +-- .../totalconnect/test_config_flow.py | 3 +-- tests/components/tplink/test_common.py | 3 ++- tests/components/tplink/test_light.py | 3 ++- tests/components/traccar/test_init.py | 4 ++-- tests/components/tradfri/test_light.py | 2 +- .../transmission/test_config_flow.py | 2 +- tests/components/transport_nsw/test_sensor.py | 2 +- tests/components/trend/test_binary_sensor.py | 2 +- tests/components/tts/test_init.py | 2 +- tests/components/twilio/test_init.py | 4 ++-- tests/components/twitch/test_twitch.py | 4 ++-- tests/components/uptime/test_sensor.py | 2 +- .../test_geo_location.py | 2 +- tests/components/utility_meter/test_init.py | 3 ++- tests/components/utility_meter/test_sensor.py | 2 +- tests/components/velbus/test_config_flow.py | 3 +-- tests/components/vera/test_binary_sensor.py | 4 ++-- tests/components/vera/test_climate.py | 4 ++-- tests/components/vera/test_config_flow.py | 3 +-- tests/components/vera/test_cover.py | 4 ++-- tests/components/vera/test_light.py | 4 ++-- tests/components/vera/test_lock.py | 4 ++-- tests/components/vera/test_scene.py | 4 ++-- tests/components/vera/test_sensor.py | 3 ++- tests/components/vera/test_switch.py | 4 ++-- .../verisure/test_ethernet_status.py | 3 ++- tests/components/verisure/test_lock.py | 3 ++- tests/components/version/test_sensor.py | 4 ++-- tests/components/vesync/test_config_flow.py | 3 +-- tests/components/vizio/test_media_player.py | 3 +-- tests/components/vultr/test_binary_sensor.py | 2 +- tests/components/vultr/test_init.py | 2 +- tests/components/vultr/test_sensor.py | 2 +- tests/components/vultr/test_switch.py | 2 +- tests/components/wake_on_lan/test_switch.py | 2 +- tests/components/webhook/test_init.py | 4 ++-- tests/components/webostv/test_media_player.py | 2 +- tests/components/websocket_api/test_init.py | 4 ++-- tests/components/withings/test_common.py | 3 +-- .../components/workday/test_binary_sensor.py | 2 +- .../xiaomi_miio/test_config_flow.py | 4 +--- tests/components/yamaha/test_media_player.py | 2 +- tests/components/yessssms/test_notify.py | 3 ++- tests/components/yr/test_sensor.py | 2 +- tests/components/zha/common.py | 3 +-- tests/components/zha/test_fan.py | 4 ++-- tests/components/zha/test_light.py | 3 +-- tests/components/zwave/conftest.py | 3 +-- tests/components/zwave/test_binary_sensor.py | 2 +- tests/components/zwave/test_cover.py | 3 +-- tests/components/zwave/test_init.py | 3 +-- tests/components/zwave/test_light.py | 7 +++--- tests/components/zwave/test_lock.py | 3 +-- tests/components/zwave/test_node_entity.py | 2 +- tests/components/zwave/test_switch.py | 3 +-- tests/helpers/test_check_config.py | 2 +- tests/helpers/test_condition.py | 4 ++-- .../helpers/test_config_entry_oauth2_flow.py | 2 +- tests/helpers/test_config_validation.py | 3 ++- tests/helpers/test_deprecation.py | 4 ++-- tests/helpers/test_device_registry.py | 2 +- tests/helpers/test_entity.py | 8 +++---- tests/helpers/test_entity_registry.py | 2 +- tests/helpers/test_event.py | 2 +- tests/helpers/test_integration_platform.py | 3 +-- tests/helpers/test_network.py | 4 ++-- tests/helpers/test_state.py | 2 +- tests/helpers/test_sun.py | 3 ++- tests/helpers/test_template.py | 3 ++- tests/mock/zwave.py | 4 ++-- tests/scripts/test_auth.py | 3 +-- tests/scripts/test_check_config.py | 2 +- tests/scripts/test_init.py | 4 ++-- tests/test_main.py | 4 ++-- tests/util/test_async.py | 3 ++- tests/util/test_init.py | 3 ++- tests/util/test_json.py | 3 ++- tests/util/test_yaml.py | 2 +- 319 files changed, 456 insertions(+), 522 deletions(-) diff --git a/tests/auth/mfa_modules/test_notify.py b/tests/auth/mfa_modules/test_notify.py index c79d76baf4f..65ee5d5d0c5 100644 --- a/tests/auth/mfa_modules/test_notify.py +++ b/tests/auth/mfa_modules/test_notify.py @@ -1,12 +1,12 @@ """Test the HMAC-based One Time Password (MFA) auth module.""" import asyncio -from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.auth import auth_manager_from_config, models as auth_models from homeassistant.auth.mfa_modules import auth_mfa_module_from_config from homeassistant.components.notify import NOTIFY_SERVICE_SCHEMA +from tests.async_mock import patch from tests.common import MockUser, async_mock_service MOCK_CODE = "123456" diff --git a/tests/auth/mfa_modules/test_totp.py b/tests/auth/mfa_modules/test_totp.py index d0a4f3cf3ac..b14b20297eb 100644 --- a/tests/auth/mfa_modules/test_totp.py +++ b/tests/auth/mfa_modules/test_totp.py @@ -1,11 +1,11 @@ """Test the Time-based One Time Password (MFA) auth module.""" import asyncio -from unittest.mock import patch from homeassistant import data_entry_flow from homeassistant.auth import auth_manager_from_config, models as auth_models from homeassistant.auth.mfa_modules import auth_mfa_module_from_config +from tests.async_mock import patch from tests.common import MockUser MOCK_CODE = "123456" diff --git a/tests/auth/test_init.py b/tests/auth/test_init.py index edcd01d51e1..f303a59179b 100644 --- a/tests/auth/test_init.py +++ b/tests/auth/test_init.py @@ -1,6 +1,5 @@ """Tests for the Home Assistant auth module.""" from datetime import timedelta -from unittest.mock import Mock, patch import jwt import pytest @@ -12,6 +11,7 @@ from homeassistant.auth.const import MFA_SESSION_EXPIRATION from homeassistant.core import callback from homeassistant.util import dt as dt_util +from tests.async_mock import Mock, patch from tests.common import CLIENT_ID, MockUser, ensure_auth_manager_loaded, flush_store diff --git a/tests/components/abode/common.py b/tests/components/abode/common.py index aabc732daa2..157a5441bb1 100644 --- a/tests/components/abode/common.py +++ b/tests/components/abode/common.py @@ -1,10 +1,9 @@ """Common methods used across tests for Abode.""" -from unittest.mock import patch - from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/abode/test_alarm_control_panel.py b/tests/components/abode/test_alarm_control_panel.py index ca546157c93..d64b211f304 100644 --- a/tests/components/abode/test_alarm_control_panel.py +++ b/tests/components/abode/test_alarm_control_panel.py @@ -1,6 +1,4 @@ """Tests for the Abode alarm control panel device.""" -from unittest.mock import PropertyMock, patch - import abodepy.helpers.constants as CONST from homeassistant.components.abode import ATTR_DEVICE_ID @@ -19,6 +17,8 @@ from homeassistant.const import ( from .common import setup_platform +from tests.async_mock import PropertyMock, patch + DEVICE_ID = "alarm_control_panel.abode_alarm" diff --git a/tests/components/abode/test_camera.py b/tests/components/abode/test_camera.py index 8b11671a456..0e843c59023 100644 --- a/tests/components/abode/test_camera.py +++ b/tests/components/abode/test_camera.py @@ -1,12 +1,12 @@ """Tests for the Abode camera device.""" -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 .common import setup_platform +from tests.async_mock import patch + async def test_entity_registry(hass): """Tests that the devices are registered in the entity registry.""" diff --git a/tests/components/abode/test_config_flow.py b/tests/components/abode/test_config_flow.py index d9762296e70..01508d412a2 100644 --- a/tests/components/abode/test_config_flow.py +++ b/tests/components/abode/test_config_flow.py @@ -1,12 +1,11 @@ """Tests for the Abode config flow.""" -from unittest.mock import patch - from abodepy.exceptions import AbodeAuthenticationException from homeassistant import data_entry_flow from homeassistant.components.abode import config_flow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_INTERNAL_SERVER_ERROR +from tests.async_mock import patch from tests.common import MockConfigEntry CONF_POLLING = "polling" diff --git a/tests/components/abode/test_cover.py b/tests/components/abode/test_cover.py index bb1b8fceffb..b166ec5464a 100644 --- a/tests/components/abode/test_cover.py +++ b/tests/components/abode/test_cover.py @@ -1,6 +1,4 @@ """Tests for the Abode cover device.""" -from unittest.mock import patch - from homeassistant.components.abode import ATTR_DEVICE_ID from homeassistant.components.cover import DOMAIN as COVER_DOMAIN from homeassistant.const import ( @@ -13,6 +11,8 @@ from homeassistant.const import ( from .common import setup_platform +from tests.async_mock import patch + DEVICE_ID = "cover.garage_door" diff --git a/tests/components/abode/test_init.py b/tests/components/abode/test_init.py index 3f73ccd77ce..1598e7bfa91 100644 --- a/tests/components/abode/test_init.py +++ b/tests/components/abode/test_init.py @@ -1,6 +1,4 @@ """Tests for the Abode module.""" -from unittest.mock import patch - from homeassistant.components.abode import ( DOMAIN as ABODE_DOMAIN, SERVICE_CAPTURE_IMAGE, @@ -11,6 +9,8 @@ from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN from .common import setup_platform +from tests.async_mock import patch + async def test_change_settings(hass): """Test change_setting service.""" diff --git a/tests/components/abode/test_light.py b/tests/components/abode/test_light.py index f0eee4b209b..6506746783c 100644 --- a/tests/components/abode/test_light.py +++ b/tests/components/abode/test_light.py @@ -1,6 +1,4 @@ """Tests for the Abode light device.""" -from unittest.mock import patch - from homeassistant.components.abode import ATTR_DEVICE_ID from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -19,6 +17,8 @@ from homeassistant.const import ( from .common import setup_platform +from tests.async_mock import patch + DEVICE_ID = "light.living_room_lamp" diff --git a/tests/components/abode/test_lock.py b/tests/components/abode/test_lock.py index 45e17861d33..6850eebe0ce 100644 --- a/tests/components/abode/test_lock.py +++ b/tests/components/abode/test_lock.py @@ -1,6 +1,4 @@ """Tests for the Abode lock device.""" -from unittest.mock import patch - from homeassistant.components.abode import ATTR_DEVICE_ID from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( @@ -13,6 +11,8 @@ from homeassistant.const import ( from .common import setup_platform +from tests.async_mock import patch + DEVICE_ID = "lock.test_lock" diff --git a/tests/components/abode/test_switch.py b/tests/components/abode/test_switch.py index 3ec9648d87d..5c480b33225 100644 --- a/tests/components/abode/test_switch.py +++ b/tests/components/abode/test_switch.py @@ -1,6 +1,4 @@ """Tests for the Abode switch device.""" -from unittest.mock import patch - from homeassistant.components.abode import ( DOMAIN as ABODE_DOMAIN, SERVICE_TRIGGER_AUTOMATION, @@ -16,6 +14,8 @@ from homeassistant.const import ( from .common import setup_platform +from tests.async_mock import patch + AUTOMATION_ID = "switch.test_automation" AUTOMATION_UID = "47fae27488f74f55b964a81a066c3a01" DEVICE_ID = "switch.test_switch" diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index c49b6ad11e9..85c80dd0b1c 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,6 +1,6 @@ """Define patches used for androidtv tests.""" -from unittest.mock import mock_open, patch +from tests.async_mock import mock_open, patch class AdbDeviceTcpFake: diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index c9f2c271000..fa4f6ffbed6 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -1,7 +1,6 @@ """The tests for the androidtv platform.""" import base64 import logging -from unittest.mock import patch from androidtv.exceptions import LockNotAcquiredException @@ -41,6 +40,8 @@ from homeassistant.setup import async_setup_component from . import patchers +from tests.async_mock import patch + # Android TV device with Python ADB implementation CONFIG_ANDROIDTV_PYTHON_ADB = { DOMAIN: { diff --git a/tests/components/api/test_init.py b/tests/components/api/test_init.py index e73b65661c9..1c93158ec03 100644 --- a/tests/components/api/test_init.py +++ b/tests/components/api/test_init.py @@ -1,7 +1,6 @@ """The tests for the Home Assistant API component.""" # pylint: disable=protected-access import json -from unittest.mock import patch from aiohttp import web import pytest @@ -12,6 +11,7 @@ from homeassistant.bootstrap import DATA_LOGGING import homeassistant.core as ha from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import async_mock_service diff --git a/tests/components/apns/test_notify.py b/tests/components/apns/test_notify.py index 61092899e24..5c69e19435e 100644 --- a/tests/components/apns/test_notify.py +++ b/tests/components/apns/test_notify.py @@ -1,7 +1,6 @@ """The tests for the APNS component.""" import io import unittest -from unittest.mock import Mock, mock_open, patch from apns2.errors import Unregistered import yaml @@ -11,6 +10,7 @@ import homeassistant.components.notify as notify from homeassistant.core import State from homeassistant.setup import setup_component +from tests.async_mock import Mock, mock_open, patch from tests.common import assert_setup_component, get_test_home_assistant CONFIG = { diff --git a/tests/components/apprise/test_notify.py b/tests/components/apprise/test_notify.py index 8135f4e8e2c..125971016cb 100644 --- a/tests/components/apprise/test_notify.py +++ b/tests/components/apprise/test_notify.py @@ -1,8 +1,8 @@ """The tests for the apprise notification platform.""" -from unittest.mock import MagicMock, patch - from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + BASE_COMPONENT = "notify" diff --git a/tests/components/aprs/test_device_tracker.py b/tests/components/aprs/test_device_tracker.py index dc0cf09f28d..95cdf4befec 100644 --- a/tests/components/aprs/test_device_tracker.py +++ b/tests/components/aprs/test_device_tracker.py @@ -1,11 +1,10 @@ """Test APRS device tracker.""" -from unittest.mock import Mock, patch - import aprslib import homeassistant.components.aprs.device_tracker as device_tracker from homeassistant.const import EVENT_HOMEASSISTANT_START +from tests.async_mock import Mock, patch from tests.common import get_test_home_assistant DEFAULT_PORT = 14580 diff --git a/tests/components/arlo/test_sensor.py b/tests/components/arlo/test_sensor.py index 15b85959ff0..e75db4a57dd 100644 --- a/tests/components/arlo/test_sensor.py +++ b/tests/components/arlo/test_sensor.py @@ -1,6 +1,5 @@ """The tests for the Netgear Arlo sensors.""" from collections import namedtuple -from unittest.mock import MagicMock, patch import pytest @@ -12,6 +11,8 @@ from homeassistant.const import ( UNIT_PERCENTAGE, ) +from tests.async_mock import patch + def _get_named_tuple(input_dict): return namedtuple("Struct", input_dict.keys())(*input_dict.values()) @@ -94,7 +95,7 @@ def sensor_with_hass_data(default_sensor, hass): def mock_dispatch(): """Mock the dispatcher connect method.""" target = "homeassistant.components.arlo.sensor.async_dispatcher_connect" - with patch(target, MagicMock()) as _mock: + with patch(target) as _mock: yield _mock diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index c860b85c240..9885c9081dd 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,13 +1,11 @@ """Tests for the Atag config flow.""" -from unittest.mock import PropertyMock - from pyatag import AtagException from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_PORT -from tests.async_mock import patch +from tests.async_mock import PropertyMock, patch from tests.common import MockConfigEntry FIXTURE_USER_INPUT = { diff --git a/tests/components/august/test_gateway.py b/tests/components/august/test_gateway.py index b9d1959d18a..ec035b9ec38 100644 --- a/tests/components/august/test_gateway.py +++ b/tests/components/august/test_gateway.py @@ -1,10 +1,8 @@ """The gateway tests for the august platform.""" -from unittest.mock import MagicMock - from homeassistant.components.august.const import DOMAIN from homeassistant.components.august.gateway import AugustGateway -from tests.async_mock import patch +from tests.async_mock import MagicMock, patch from tests.components.august.mocks import _mock_august_authentication, _mock_get_config diff --git a/tests/components/auth/test_init.py b/tests/components/auth/test_init.py index 2c9a39c6fb6..3d799fe0078 100644 --- a/tests/components/auth/test_init.py +++ b/tests/components/auth/test_init.py @@ -1,6 +1,5 @@ """Integration tests for the auth component.""" from datetime import timedelta -from unittest.mock import patch from homeassistant.auth.models import Credentials from homeassistant.components import auth @@ -10,6 +9,7 @@ from homeassistant.util.dt import utcnow from . import async_setup_auth +from tests.async_mock import patch from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI, MockUser diff --git a/tests/components/auth/test_login_flow.py b/tests/components/auth/test_login_flow.py index e6e5281d601..f2629a27bb9 100644 --- a/tests/components/auth/test_login_flow.py +++ b/tests/components/auth/test_login_flow.py @@ -1,8 +1,7 @@ """Tests for the login flow.""" -from unittest.mock import patch - from . import async_setup_auth +from tests.async_mock import patch from tests.common import CLIENT_ID, CLIENT_REDIRECT_URI diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py index b0db66e16a9..d7bdfbeef3e 100644 --- a/tests/components/automation/test_homeassistant.py +++ b/tests/components/automation/test_homeassistant.py @@ -1,11 +1,9 @@ """The tests for the Event automation.""" -from unittest.mock import patch - import homeassistant.components.automation as automation from homeassistant.core import CoreState from homeassistant.setup import async_setup_component -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch from tests.common import async_mock_service diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index a039604525f..7a082ba1931 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,6 +1,5 @@ """The tests for the automation component.""" from datetime import timedelta -from unittest.mock import Mock, patch import pytest @@ -19,6 +18,7 @@ from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 1173de4b02a..46b774c1cc9 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,6 +1,5 @@ """The tests for numeric state automation.""" from datetime import timedelta -from unittest.mock import patch import pytest import voluptuous as vol @@ -11,6 +10,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 033ce44e5a4..0d591c967be 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,6 +1,5 @@ """The test for state automation.""" from datetime import timedelta -from unittest.mock import patch import pytest @@ -9,6 +8,7 @@ from homeassistant.core import Context from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 4cb2672ab64..4efb19ff201 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -1,6 +1,5 @@ """The tests for the sun automation.""" from datetime import datetime -from unittest.mock import patch import pytest @@ -10,6 +9,7 @@ from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed, async_mock_service, mock_component from tests.components.automation import common diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index ec8d504652b..0ba85467fcd 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -1,6 +1,5 @@ """The tests for the time automation.""" from datetime import timedelta -from unittest.mock import patch import pytest @@ -8,6 +7,7 @@ import homeassistant.components.automation as automation from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, async_fire_time_changed, diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index 5803f31d526..b8baf18a67d 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -1,12 +1,10 @@ """Test Axis component setup process.""" -from unittest.mock import Mock, patch - from homeassistant.components import axis from homeassistant.setup import async_setup_component from .test_device import MAC, setup_axis_integration -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 844cfedf7fe..d8d69265f3a 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,13 +1,13 @@ """Axis switch platform tests.""" -from unittest.mock import Mock, call as mock_call - from homeassistant.components import axis import homeassistant.components.switch as switch from homeassistant.setup import async_setup_component from .test_device import NAME, setup_axis_integration +from tests.async_mock import Mock, call as mock_call + EVENTS = [ { "operation": "Initialized", diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 1ac24e03702..968b54b7892 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -1,6 +1,5 @@ """The test for binary_sensor device automation.""" from datetime import timedelta -from unittest.mock import patch import pytest @@ -12,6 +11,7 @@ from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_get_device_automation_capabilities, diff --git a/tests/components/bluetooth_le_tracker/test_device_tracker.py b/tests/components/bluetooth_le_tracker/test_device_tracker.py index 308371c9aaa..af4df463339 100644 --- a/tests/components/bluetooth_le_tracker/test_device_tracker.py +++ b/tests/components/bluetooth_le_tracker/test_device_tracker.py @@ -1,7 +1,6 @@ """Test Bluetooth LE device tracker.""" from datetime import timedelta -from unittest.mock import patch from homeassistant.components.bluetooth_le_tracker import device_tracker from homeassistant.components.device_tracker.const import ( @@ -13,6 +12,7 @@ from homeassistant.const import CONF_PLATFORM from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util, slugify +from tests.async_mock import patch from tests.common import async_fire_time_changed diff --git a/tests/components/bom/test_sensor.py b/tests/components/bom/test_sensor.py index 7a9daa26c2b..6e85dbca1cd 100644 --- a/tests/components/bom/test_sensor.py +++ b/tests/components/bom/test_sensor.py @@ -2,7 +2,6 @@ import json import re import unittest -from unittest.mock import patch from urllib.parse import urlparse import requests @@ -11,6 +10,7 @@ from homeassistant.components import sensor from homeassistant.components.bom.sensor import BOMCurrentData from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant, load_fixture VALID_CONFIG = { diff --git a/tests/components/broadlink/test_init.py b/tests/components/broadlink/test_init.py index 1bdad193f52..c5477dff49f 100644 --- a/tests/components/broadlink/test_init.py +++ b/tests/components/broadlink/test_init.py @@ -1,7 +1,6 @@ """The tests for the broadlink component.""" from base64 import b64decode from datetime import timedelta -from unittest.mock import MagicMock, call, patch import pytest @@ -9,6 +8,8 @@ from homeassistant.components.broadlink import async_setup_service, data_packet from homeassistant.components.broadlink.const import DOMAIN, SERVICE_LEARN, SERVICE_SEND from homeassistant.util.dt import utcnow +from tests.async_mock import MagicMock, call, patch + DUMMY_IR_PACKET = ( "JgBGAJKVETkRORA6ERQRFBEUERQRFBE5ETkQOhAVEBUQFREUEBUQ" "OhEUERQRORE5EBURFBA6EBUQOhE5EBUQFRA6EDoRFBEADQUAAA==" diff --git a/tests/components/caldav/test_calendar.py b/tests/components/caldav/test_calendar.py index 8e88c6e564e..1b6c21c7358 100644 --- a/tests/components/caldav/test_calendar.py +++ b/tests/components/caldav/test_calendar.py @@ -1,6 +1,5 @@ """The tests for the webdav calendar component.""" import datetime -from unittest.mock import MagicMock, Mock from caldav.objects import Event import pytest @@ -9,7 +8,7 @@ from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import async_setup_component from homeassistant.util import dt -from tests.async_mock import patch +from tests.async_mock import MagicMock, Mock, patch # pylint: disable=redefined-outer-name diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 401350ec93c..3eaef6575ac 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -2,7 +2,6 @@ import asyncio import base64 import io -from unittest.mock import PropertyMock, mock_open import pytest @@ -14,7 +13,7 @@ from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -from tests.async_mock import patch +from tests.async_mock import PropertyMock, mock_open, patch from tests.components.camera import common diff --git a/tests/components/canary/test_init.py b/tests/components/canary/test_init.py index 819d1ce0e90..a3f6fbd7e2d 100644 --- a/tests/components/canary/test_init.py +++ b/tests/components/canary/test_init.py @@ -1,10 +1,10 @@ """The tests for the Canary component.""" import unittest -from unittest.mock import MagicMock, PropertyMock, patch from homeassistant import setup import homeassistant.components.canary as canary +from tests.async_mock import MagicMock, PropertyMock, patch from tests.common import get_test_home_assistant diff --git a/tests/components/canary/test_sensor.py b/tests/components/canary/test_sensor.py index 1d559dbb7ba..5a4a82ccc5a 100644 --- a/tests/components/canary/test_sensor.py +++ b/tests/components/canary/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the Canary sensor platform.""" import copy import unittest -from unittest.mock import Mock from homeassistant.components.canary import DATA_CANARY, sensor as canary from homeassistant.components.canary.sensor import ( @@ -14,6 +13,7 @@ from homeassistant.components.canary.sensor import ( ) from homeassistant.const import TEMP_CELSIUS, UNIT_PERCENTAGE +from tests.async_mock import Mock from tests.common import get_test_home_assistant from tests.components.canary.test_init import mock_device, mock_location diff --git a/tests/components/cast/test_home_assistant_cast.py b/tests/components/cast/test_home_assistant_cast.py index 2ec02da7669..50685d5f3e7 100644 --- a/tests/components/cast/test_home_assistant_cast.py +++ b/tests/components/cast/test_home_assistant_cast.py @@ -1,8 +1,7 @@ """Test Home Assistant Cast.""" -from unittest.mock import Mock, patch - from homeassistant.components.cast import home_assistant_cast +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry, async_mock_signal diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index b5ec45d3aa0..e42bf8c7e3c 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -1,6 +1,5 @@ """The tests for the climate component.""" from typing import List -from unittest.mock import MagicMock import pytest import voluptuous as vol @@ -13,6 +12,7 @@ from homeassistant.components.climate import ( ClimateEntity, ) +from tests.async_mock import MagicMock from tests.common import async_mock_service diff --git a/tests/components/cloud/conftest.py b/tests/components/cloud/conftest.py index 4755d470418..02d9b4c41aa 100644 --- a/tests/components/cloud/conftest.py +++ b/tests/components/cloud/conftest.py @@ -1,6 +1,4 @@ """Fixtures for cloud tests.""" -from unittest.mock import patch - import jwt import pytest @@ -8,6 +6,8 @@ from homeassistant.components.cloud import const, prefs from . import mock_cloud, mock_cloud_prefs +from tests.async_mock import patch + @pytest.fixture(autouse=True) def mock_user_data(): diff --git a/tests/components/cloud/test_binary_sensor.py b/tests/components/cloud/test_binary_sensor.py index 38c3067873f..6a2d76dc403 100644 --- a/tests/components/cloud/test_binary_sensor.py +++ b/tests/components/cloud/test_binary_sensor.py @@ -1,10 +1,8 @@ """Tests for the cloud binary sensor.""" -from unittest.mock import Mock - from homeassistant.components.cloud.const import DISPATCHER_REMOTE_UPDATE from homeassistant.setup import async_setup_component -from tests.async_mock import patch +from tests.async_mock import Mock, patch async def test_remote_connection_sensor(hass): diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 9866270473d..3808f9b179c 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -1,6 +1,4 @@ """Test the Cloud Google Config.""" -from unittest.mock import Mock - from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig from homeassistant.components.google_assistant import helpers as ga_helpers @@ -9,7 +7,7 @@ from homeassistant.core import CoreState from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.util.dt import utcnow -from tests.async_mock import AsyncMock, patch +from tests.async_mock import AsyncMock, Mock, patch from tests.common import async_fire_time_changed diff --git a/tests/components/cloud/test_prefs.py b/tests/components/cloud/test_prefs.py index d1b6f9ed867..4f2d5d6d661 100644 --- a/tests/components/cloud/test_prefs.py +++ b/tests/components/cloud/test_prefs.py @@ -1,9 +1,9 @@ """Test Cloud preferences.""" -from unittest.mock import patch - from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components.cloud.prefs import STORAGE_KEY, CloudPreferences +from tests.async_mock import patch + async def test_set_username(hass): """Test we clear config if we set different username.""" diff --git a/tests/components/coinmarketcap/test_sensor.py b/tests/components/coinmarketcap/test_sensor.py index 9d1e89fbc24..8997bc4a5d6 100644 --- a/tests/components/coinmarketcap/test_sensor.py +++ b/tests/components/coinmarketcap/test_sensor.py @@ -1,11 +1,11 @@ """Tests for the CoinMarketCap sensor platform.""" import json import unittest -from unittest.mock import patch import homeassistant.components.sensor as sensor from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant, load_fixture VALID_CONFIG = { diff --git a/tests/components/command_line/test_notify.py b/tests/components/command_line/test_notify.py index 0cb7e9293e9..f20011d4482 100644 --- a/tests/components/command_line/test_notify.py +++ b/tests/components/command_line/test_notify.py @@ -2,11 +2,11 @@ import os import tempfile import unittest -from unittest.mock import patch import homeassistant.components.notify as notify from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/command_line/test_sensor.py b/tests/components/command_line/test_sensor.py index b923be81888..bbf69dc73a0 100644 --- a/tests/components/command_line/test_sensor.py +++ b/tests/components/command_line/test_sensor.py @@ -1,10 +1,10 @@ """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.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/config/test_group.py b/tests/components/config/test_group.py index f555660fb7a..98ad2041713 100644 --- a/tests/components/config/test_group.py +++ b/tests/components/config/test_group.py @@ -1,11 +1,10 @@ """Test Group config panel.""" import json -from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import config -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch VIEW_NAME = "api:config:group:config" diff --git a/tests/components/config/test_script.py b/tests/components/config/test_script.py index 0026729766c..4dc906e92f3 100644 --- a/tests/components/config/test_script.py +++ b/tests/components/config/test_script.py @@ -1,9 +1,9 @@ """Tests for config/script.""" -from unittest.mock import patch - from homeassistant.bootstrap import async_setup_component from homeassistant.components import config +from tests.async_mock import patch + async def test_delete_script(hass, hass_client): """Test deleting a script.""" diff --git a/tests/components/config/test_zwave.py b/tests/components/config/test_zwave.py index 3624554843a..75a66f61939 100644 --- a/tests/components/config/test_zwave.py +++ b/tests/components/config/test_zwave.py @@ -1,6 +1,5 @@ """Test Z-Wave config panel.""" import json -from unittest.mock import MagicMock, patch import pytest @@ -9,6 +8,7 @@ from homeassistant.components import config from homeassistant.components.zwave import DATA_NETWORK, const from homeassistant.const import HTTP_NOT_FOUND +from tests.async_mock import MagicMock, patch from tests.mock.zwave import MockEntityValues, MockNode, MockValue VIEW_NAME = "api:config:zwave:device_config" diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index aea78f17564..996aef3db4b 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -1,7 +1,6 @@ # pylint: disable=redefined-outer-name """Tests for the Daikin config flow.""" import asyncio -from unittest.mock import patch import pytest @@ -10,6 +9,7 @@ from homeassistant.components.daikin import config_flow from homeassistant.components.daikin.const import KEY_IP, KEY_MAC from homeassistant.const import CONF_HOST +from tests.async_mock import patch from tests.common import MockConfigEntry MAC = "AABBCCDDEEFF" diff --git a/tests/components/darksky/test_sensor.py b/tests/components/darksky/test_sensor.py index 2dc2a3ff30b..b4707af01b2 100644 --- a/tests/components/darksky/test_sensor.py +++ b/tests/components/darksky/test_sensor.py @@ -2,7 +2,6 @@ from datetime import timedelta import re import unittest -from unittest.mock import MagicMock, patch import forecastio from requests.exceptions import HTTPError @@ -11,6 +10,7 @@ import requests_mock from homeassistant.components.darksky import sensor as darksky from homeassistant.setup import setup_component +from tests.async_mock import MagicMock, patch from tests.common import get_test_home_assistant, load_fixture VALID_CONFIG_MINIMAL = { diff --git a/tests/components/darksky/test_weather.py b/tests/components/darksky/test_weather.py index 09ffe7bdc90..f871d424db6 100644 --- a/tests/components/darksky/test_weather.py +++ b/tests/components/darksky/test_weather.py @@ -1,7 +1,6 @@ """The tests for the Dark Sky weather component.""" import re import unittest -from unittest.mock import patch import forecastio from requests.exceptions import ConnectionError @@ -11,6 +10,7 @@ from homeassistant.components import weather from homeassistant.setup import setup_component from homeassistant.util.unit_system import METRIC_SYSTEM +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture diff --git a/tests/components/default_config/test_init.py b/tests/components/default_config/test_init.py index 8e0862eb0cb..00fb1c1047b 100644 --- a/tests/components/default_config/test_init.py +++ b/tests/components/default_config/test_init.py @@ -1,10 +1,10 @@ """Test the default_config init.""" -from unittest.mock import patch - import pytest from homeassistant.setup import async_setup_component +from tests.async_mock import patch + @pytest.fixture(autouse=True) def recorder_url_mock(): diff --git a/tests/components/demo/test_camera.py b/tests/components/demo/test_camera.py index d46d1fdc62b..49b8e017f1a 100644 --- a/tests/components/demo/test_camera.py +++ b/tests/components/demo/test_camera.py @@ -1,6 +1,4 @@ """The tests for local file camera component.""" -from unittest.mock import patch - import pytest from homeassistant.components.camera import ( @@ -18,6 +16,8 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component +from tests.async_mock import patch + ENTITY_CAMERA = "camera.demo_camera" diff --git a/tests/components/demo/test_geo_location.py b/tests/components/demo/test_geo_location.py index 5d5f2fc8106..0ba3a35b891 100644 --- a/tests/components/demo/test_geo_location.py +++ b/tests/components/demo/test_geo_location.py @@ -1,6 +1,5 @@ """The tests for the demo platform.""" import unittest -from unittest.mock import patch from homeassistant.components import geo_location from homeassistant.components.demo.geo_location import ( @@ -11,6 +10,7 @@ from homeassistant.const import LENGTH_KILOMETERS from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, fire_time_changed, diff --git a/tests/components/demo/test_notify.py b/tests/components/demo/test_notify.py index e30d65112e8..7c7b7fa0aa1 100644 --- a/tests/components/demo/test_notify.py +++ b/tests/components/demo/test_notify.py @@ -1,6 +1,5 @@ """The tests for the notify demo platform.""" import unittest -from unittest.mock import patch import pytest import voluptuous as vol @@ -11,6 +10,7 @@ from homeassistant.core import callback from homeassistant.helpers import discovery from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant from tests.components.notify import common diff --git a/tests/components/denonavr/test_media_player.py b/tests/components/denonavr/test_media_player.py index 91bc2abf94d..1547391a339 100644 --- a/tests/components/denonavr/test_media_player.py +++ b/tests/components/denonavr/test_media_player.py @@ -1,6 +1,4 @@ """The tests for the denonavr media player platform.""" -from unittest.mock import patch - import pytest from homeassistant.components import media_player @@ -8,6 +6,8 @@ from homeassistant.components.denonavr import ATTR_COMMAND, DOMAIN, SERVICE_GET_ from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_PLATFORM from homeassistant.setup import async_setup_component +from tests.async_mock import patch + NAME = "fake" ENTITY_ID = f"{media_player.DOMAIN}.{NAME}" diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index 466f07a9deb..96fee84126a 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -1,11 +1,12 @@ """The tests for the derivative sensor platform.""" from datetime import timedelta -from unittest.mock import patch from homeassistant.const import POWER_WATT, TIME_HOURS, TIME_MINUTES, TIME_SECONDS from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch + async def test_state(hass): """Test derivative sensor state.""" diff --git a/tests/components/dialogflow/test_init.py b/tests/components/dialogflow/test_init.py index 5c2a71ad88f..c8c508a0d88 100644 --- a/tests/components/dialogflow/test_init.py +++ b/tests/components/dialogflow/test_init.py @@ -1,7 +1,6 @@ """The tests for the Dialogflow component.""" import copy import json -from unittest.mock import Mock import pytest @@ -10,6 +9,8 @@ from homeassistant.components import dialogflow, intent_script from homeassistant.core import callback from homeassistant.setup import async_setup_component +from tests.async_mock import Mock + SESSION_ID = "a9b84cec-46b6-484e-8f31-f65dba03ae6d" INTENT_ID = "c6a74079-a8f0-46cd-b372-5a934d23591c" INTENT_NAME = "tests" diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 936a1e68037..895a95bef7b 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -9,7 +9,6 @@ import asyncio import datetime from decimal import Decimal from itertools import chain, repeat -from unittest.mock import DEFAULT, Mock import pytest @@ -18,6 +17,7 @@ from homeassistant.components.dsmr.sensor import DerivativeDSMREntity from homeassistant.const import ENERGY_KILO_WATT_HOUR, TIME_HOURS, VOLUME_CUBIC_METERS import tests.async_mock +from tests.async_mock import DEFAULT, Mock from tests.common import assert_setup_component diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 052228e7aab..67baed7878e 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -2,7 +2,6 @@ from datetime import timedelta from ipaddress import ip_address import json -from unittest.mock import patch from aiohttp.hdrs import CONTENT_TYPE import pytest @@ -42,6 +41,7 @@ from homeassistant.const import ( ) import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( async_fire_time_changed, async_mock_service, diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 6fa6d969539..b1cf2aacb1b 100644 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -1,8 +1,8 @@ """Test the Emulated Hue component.""" -from unittest.mock import MagicMock, Mock, patch - from homeassistant.components.emulated_hue import Config +from tests.async_mock import MagicMock, Mock, patch + def test_config_google_home_entity_id_to_number(): """Test config adheres to the type.""" diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 889f6437b0a..2557ecac2dd 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -1,7 +1,6 @@ """The tests for the emulated Hue component.""" import json import unittest -from unittest.mock import patch from aiohttp.hdrs import CONTENT_TYPE import defusedxml.ElementTree as ET @@ -10,6 +9,7 @@ import requests from homeassistant import const, setup from homeassistant.components import emulated_hue +from tests.async_mock import patch from tests.common import get_test_home_assistant, get_test_instance_port HTTP_SERVER_PORT = get_test_instance_port() diff --git a/tests/components/emulated_roku/test_binding.py b/tests/components/emulated_roku/test_binding.py index 61c93690548..5ff29194adf 100644 --- a/tests/components/emulated_roku/test_binding.py +++ b/tests/components/emulated_roku/test_binding.py @@ -1,6 +1,4 @@ """Tests for emulated_roku library bindings.""" -from unittest.mock import Mock, patch - from homeassistant.components.emulated_roku.binding import ( ATTR_APP_ID, ATTR_COMMAND_TYPE, @@ -14,7 +12,7 @@ from homeassistant.components.emulated_roku.binding import ( EmulatedRoku, ) -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock, patch async def test_events_fired_properly(hass): diff --git a/tests/components/emulated_roku/test_init.py b/tests/components/emulated_roku/test_init.py index 8d58519ddf9..92952a5d840 100644 --- a/tests/components/emulated_roku/test_init.py +++ b/tests/components/emulated_roku/test_init.py @@ -1,10 +1,8 @@ """Test emulated_roku component setup process.""" -from unittest.mock import Mock, patch - from homeassistant.components import emulated_roku from homeassistant.setup import async_setup_component -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock, patch async def test_config_required_fields(hass): diff --git a/tests/components/facebox/test_image_processing.py b/tests/components/facebox/test_image_processing.py index 8506cd2d817..b40211d5a91 100644 --- a/tests/components/facebox/test_image_processing.py +++ b/tests/components/facebox/test_image_processing.py @@ -1,6 +1,4 @@ """The tests for the facebox component.""" -from unittest.mock import Mock, mock_open, patch - import pytest import requests import requests_mock @@ -23,6 +21,8 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, mock_open, patch + MOCK_IP = "192.168.0.1" MOCK_PORT = "8080" diff --git a/tests/components/fail2ban/test_sensor.py b/tests/components/fail2ban/test_sensor.py index fc8bfc318bb..b164cc93f2e 100644 --- a/tests/components/fail2ban/test_sensor.py +++ b/tests/components/fail2ban/test_sensor.py @@ -1,6 +1,5 @@ """The tests for local file sensor platform.""" import unittest -from unittest.mock import Mock, patch from mock_open import MockOpen @@ -12,6 +11,7 @@ from homeassistant.components.fail2ban.sensor import ( ) from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/feedreader/test_init.py b/tests/components/feedreader/test_init.py index 58a660fcb5d..823bdf6eb63 100644 --- a/tests/components/feedreader/test_init.py +++ b/tests/components/feedreader/test_init.py @@ -6,7 +6,6 @@ from os.path import exists import time import unittest from unittest import mock -from unittest.mock import patch from homeassistant.components import feedreader from homeassistant.components.feedreader import ( @@ -22,6 +21,7 @@ from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START from homeassistant.core import callback from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture _LOGGER = getLogger(__name__) diff --git a/tests/components/ffmpeg/test_init.py b/tests/components/ffmpeg/test_init.py index 3c6a2fbb92d..4187fe561cc 100644 --- a/tests/components/ffmpeg/test_init.py +++ b/tests/components/ffmpeg/test_init.py @@ -1,6 +1,4 @@ """The tests for Home Assistant ffmpeg.""" -from unittest.mock import MagicMock - import homeassistant.components.ffmpeg as ffmpeg from homeassistant.components.ffmpeg import ( DOMAIN, @@ -12,6 +10,7 @@ from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import callback from homeassistant.setup import async_setup_component, setup_component +from tests.async_mock import MagicMock from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index 9a316a85735..b57232d25ad 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -1,11 +1,11 @@ """The test for the fido sensor platform.""" import logging import sys -from unittest.mock import MagicMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components.fido import sensor as fido +from tests.async_mock import MagicMock, patch from tests.common import assert_setup_component CONTRACT = "123456789" diff --git a/tests/components/file/test_notify.py b/tests/components/file/test_notify.py index bd5ae68cb37..e4ae125949a 100644 --- a/tests/components/file/test_notify.py +++ b/tests/components/file/test_notify.py @@ -1,13 +1,13 @@ """The tests for the notify file platform.""" import os import unittest -from unittest.mock import call, mock_open, patch import homeassistant.components.notify as notify from homeassistant.components.notify import ATTR_TITLE_DEFAULT from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import call, mock_open, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/file/test_sensor.py b/tests/components/file/test_sensor.py index 3afdd8284fc..416f3e8c721 100644 --- a/tests/components/file/test_sensor.py +++ b/tests/components/file/test_sensor.py @@ -1,6 +1,5 @@ """The tests for local file sensor platform.""" import unittest -from unittest.mock import Mock, patch # Using third party package because of a bug reading binary data in Python 3.4 # https://bugs.python.org/issue23004 @@ -9,6 +8,7 @@ from mock_open import MockOpen from homeassistant.const import STATE_UNKNOWN from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import get_test_home_assistant, mock_registry diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 06bf7cfaf12..238cb366f73 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -1,7 +1,6 @@ """The test for the data filter sensor platform.""" from datetime import timedelta import unittest -from unittest.mock import patch from homeassistant.components.filter.sensor import ( LowPassFilter, @@ -15,6 +14,7 @@ import homeassistant.core as ha from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( assert_setup_component, get_test_home_assistant, diff --git a/tests/components/folder_watcher/test_init.py b/tests/components/folder_watcher/test_init.py index fa2cfc6d3f1..997bac23f22 100644 --- a/tests/components/folder_watcher/test_init.py +++ b/tests/components/folder_watcher/test_init.py @@ -1,10 +1,11 @@ """The tests for the folder_watcher component.""" import os -from unittest.mock import Mock, patch from homeassistant.components import folder_watcher from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch + async def test_invalid_path_setup(hass): """Test that an invalid path is not set up.""" diff --git a/tests/components/foobot/test_sensor.py b/tests/components/foobot/test_sensor.py index 37a0310d163..0a145c88479 100644 --- a/tests/components/foobot/test_sensor.py +++ b/tests/components/foobot/test_sensor.py @@ -2,7 +2,6 @@ import asyncio import re -from unittest.mock import MagicMock import pytest @@ -20,6 +19,7 @@ from homeassistant.const import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock from tests.common import load_fixture VALID_CONFIG = { diff --git a/tests/components/freebox/conftest.py b/tests/components/freebox/conftest.py index e813469cbbf..7581b03ce72 100644 --- a/tests/components/freebox/conftest.py +++ b/tests/components/freebox/conftest.py @@ -1,8 +1,8 @@ """Test helpers for Freebox.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + @pytest.fixture(autouse=True) def mock_path(): diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index f19e05b84df..066b9a30cb3 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -1,9 +1,9 @@ """Tests for the AVM Fritz!Box integration.""" -from unittest.mock import Mock - from homeassistant.components.fritzbox.const import DOMAIN from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import Mock + MOCK_CONFIG = { DOMAIN: { CONF_DEVICES: [ diff --git a/tests/components/fritzbox/conftest.py b/tests/components/fritzbox/conftest.py index 591c1037525..7dcee138382 100644 --- a/tests/components/fritzbox/conftest.py +++ b/tests/components/fritzbox/conftest.py @@ -1,8 +1,8 @@ """Fixtures for the AVM Fritz!Box integration.""" -from unittest.mock import Mock, patch - import pytest +from tests.async_mock import Mock, patch + @pytest.fixture(name="fritz") def fritz_fixture() -> Mock: diff --git a/tests/components/fritzbox/test_binary_sensor.py b/tests/components/fritzbox/test_binary_sensor.py index 89c1dea1704..b3157a3be33 100644 --- a/tests/components/fritzbox/test_binary_sensor.py +++ b/tests/components/fritzbox/test_binary_sensor.py @@ -1,7 +1,6 @@ """Tests for AVM Fritz!Box binary sensor component.""" from datetime import timedelta from unittest import mock -from unittest.mock import Mock from requests.exceptions import HTTPError @@ -19,6 +18,7 @@ import homeassistant.util.dt as dt_util from . import MOCK_CONFIG, FritzDeviceBinarySensorMock +from tests.async_mock import Mock from tests.common import async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake_name" diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index 627eae5da91..519e3afa31a 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -1,6 +1,5 @@ """Tests for AVM Fritz!Box climate component.""" from datetime import timedelta -from unittest.mock import Mock, call from requests.exceptions import HTTPError @@ -42,6 +41,7 @@ import homeassistant.util.dt as dt_util from . import MOCK_CONFIG, FritzDeviceClimateMock +from tests.async_mock import Mock, call from tests.common import async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake_name" diff --git a/tests/components/fritzbox/test_config_flow.py b/tests/components/fritzbox/test_config_flow.py index 8bfd992347f..35b41d52118 100644 --- a/tests/components/fritzbox/test_config_flow.py +++ b/tests/components/fritzbox/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for AVM Fritz!Box config flow.""" from unittest import mock -from unittest.mock import Mock, patch from pyfritzhome import LoginError import pytest @@ -17,6 +16,8 @@ from homeassistant.helpers.typing import HomeAssistantType from . import MOCK_CONFIG +from tests.async_mock import Mock, patch + MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] MOCK_SSDP_DATA = { ATTR_SSDP_LOCATION: "https://fake_host:12345/test", diff --git a/tests/components/fritzbox/test_init.py b/tests/components/fritzbox/test_init.py index 11067c1aa51..55dab3626db 100644 --- a/tests/components/fritzbox/test_init.py +++ b/tests/components/fritzbox/test_init.py @@ -1,6 +1,4 @@ """Tests for the AVM Fritz!Box integration.""" -from unittest.mock import Mock, call - from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ENTRY_STATE_LOADED, ENTRY_STATE_NOT_LOADED @@ -10,6 +8,7 @@ from homeassistant.setup import async_setup_component from . import MOCK_CONFIG, FritzDeviceSwitchMock +from tests.async_mock import Mock, call from tests.common import MockConfigEntry diff --git a/tests/components/fritzbox/test_sensor.py b/tests/components/fritzbox/test_sensor.py index 6dde22f074e..7f97e8abfb1 100644 --- a/tests/components/fritzbox/test_sensor.py +++ b/tests/components/fritzbox/test_sensor.py @@ -1,6 +1,5 @@ """Tests for AVM Fritz!Box sensor component.""" from datetime import timedelta -from unittest.mock import Mock from requests.exceptions import HTTPError @@ -21,6 +20,7 @@ import homeassistant.util.dt as dt_util from . import MOCK_CONFIG, FritzDeviceSensorMock +from tests.async_mock import Mock from tests.common import async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake_name" diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index 1c0f7b3f37a..c9e05b2d481 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -1,6 +1,5 @@ """Tests for AVM Fritz!Box switch component.""" from datetime import timedelta -from unittest.mock import Mock from requests.exceptions import HTTPError @@ -29,6 +28,7 @@ import homeassistant.util.dt as dt_util from . import MOCK_CONFIG, FritzDeviceSwitchMock +from tests.async_mock import Mock from tests.common import async_fire_time_changed ENTITY_ID = f"{DOMAIN}.fake_name" diff --git a/tests/components/garmin_connect/test_config_flow.py b/tests/components/garmin_connect/test_config_flow.py index 276b6f46871..1c383d4343a 100644 --- a/tests/components/garmin_connect/test_config_flow.py +++ b/tests/components/garmin_connect/test_config_flow.py @@ -1,6 +1,4 @@ """Test the Garmin Connect config flow.""" -from unittest.mock import patch - from garminconnect import ( GarminConnectAuthenticationError, GarminConnectConnectionError, @@ -12,6 +10,7 @@ from homeassistant import data_entry_flow from homeassistant.components.garmin_connect.const import DOMAIN from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry MOCK_CONF = { diff --git a/tests/components/gdacs/__init__.py b/tests/components/gdacs/__init__.py index 6e61b86dbb7..648ab08507b 100644 --- a/tests/components/gdacs/__init__.py +++ b/tests/components/gdacs/__init__.py @@ -1,5 +1,5 @@ """Tests for the GDACS component.""" -from unittest.mock import MagicMock +from tests.async_mock import MagicMock def _generate_mock_feed_entry( diff --git a/tests/components/geo_rss_events/test_sensor.py b/tests/components/geo_rss_events/test_sensor.py index 25243afea78..9f7cdd3faab 100644 --- a/tests/components/geo_rss_events/test_sensor.py +++ b/tests/components/geo_rss_events/test_sensor.py @@ -1,7 +1,6 @@ """The test for the geo rss events sensor platform.""" import unittest from unittest import mock -from unittest.mock import MagicMock, patch from homeassistant.components import sensor import homeassistant.components.geo_rss_events.sensor as geo_rss_events @@ -14,6 +13,7 @@ from homeassistant.const import ( from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, patch from tests.common import ( assert_setup_component, fire_time_changed, diff --git a/tests/components/geofency/test_init.py b/tests/components/geofency/test_init.py index b988d613d6c..bdb5b49d11b 100644 --- a/tests/components/geofency/test_init.py +++ b/tests/components/geofency/test_init.py @@ -1,7 +1,4 @@ """The tests for the Geofency device tracker platform.""" -# pylint: disable=redefined-outer-name -from unittest.mock import Mock, patch - import pytest from homeassistant import data_entry_flow @@ -16,6 +13,9 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component from homeassistant.util import slugify +# pylint: disable=redefined-outer-name +from tests.async_mock import Mock, patch + HOME_LATITUDE = 37.239622 HOME_LONGITUDE = -115.815811 diff --git a/tests/components/geonetnz_quakes/__init__.py b/tests/components/geonetnz_quakes/__init__.py index 424c6372ea8..82cb62b3939 100644 --- a/tests/components/geonetnz_quakes/__init__.py +++ b/tests/components/geonetnz_quakes/__init__.py @@ -1,5 +1,5 @@ """Tests for the geonetnz_quakes component.""" -from unittest.mock import MagicMock +from tests.async_mock import MagicMock def _generate_mock_feed_entry( diff --git a/tests/components/geonetnz_volcano/__init__.py b/tests/components/geonetnz_volcano/__init__.py index 708b69e0031..023cab46ec8 100644 --- a/tests/components/geonetnz_volcano/__init__.py +++ b/tests/components/geonetnz_volcano/__init__.py @@ -1,5 +1,5 @@ """The tests for the GeoNet NZ Volcano Feed integration.""" -from unittest.mock import MagicMock +from tests.async_mock import MagicMock def _generate_mock_feed_entry( diff --git a/tests/components/google/conftest.py b/tests/components/google/conftest.py index 20cb13130ec..6ec75ad53f6 100644 --- a/tests/components/google/conftest.py +++ b/tests/components/google/conftest.py @@ -1,8 +1,8 @@ """Test configuration and mocks for the google integration.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + TEST_CALENDAR = { "id": "qwertyuiopasdfghjklzxcvbnm@import.calendar.google.com", "etag": '"3584134138943410"', diff --git a/tests/components/google/test_calendar.py b/tests/components/google/test_calendar.py index ad7b6b12001..92f03396965 100644 --- a/tests/components/google/test_calendar.py +++ b/tests/components/google/test_calendar.py @@ -1,6 +1,5 @@ """The tests for the google calendar platform.""" import copy -from unittest.mock import Mock, patch import httplib2 import pytest @@ -23,6 +22,7 @@ from homeassistant.setup import async_setup_component from homeassistant.util import slugify import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import async_mock_service GOOGLE_CONFIG = {CONF_CLIENT_ID: "client_id", CONF_CLIENT_SECRET: "client_secret"} diff --git a/tests/components/google/test_init.py b/tests/components/google/test_init.py index 59a9f9f5ab2..6f7ce74ce62 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -1,11 +1,11 @@ """The tests for the Google Calendar component.""" -from unittest.mock import patch - import pytest import homeassistant.components.google as google from homeassistant.setup import async_setup_component +from tests.async_mock import patch + @pytest.fixture(name="google_setup") def mock_google_setup(hass): diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index f133db26d89..f4e54176b19 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,6 +1,5 @@ """Tests for the Google Assistant traits.""" import logging -from unittest.mock import Mock import pytest @@ -45,7 +44,7 @@ from homeassistant.util import color from . import BASIC_CONFIG, MockConfig -from tests.async_mock import patch +from tests.async_mock import Mock, patch from tests.common import async_mock_service _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index 6703852528c..280258125df 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -2,7 +2,6 @@ import asyncio import os import shutil -from unittest.mock import patch from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, @@ -12,6 +11,7 @@ from homeassistant.components.media_player.const import ( import homeassistant.components.tts as tts from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant, mock_service from tests.components.tts.test_init import mutagen_mock # noqa: F401 diff --git a/tests/components/google_wifi/test_sensor.py b/tests/components/google_wifi/test_sensor.py index 22059706dc5..69db8de184b 100644 --- a/tests/components/google_wifi/test_sensor.py +++ b/tests/components/google_wifi/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the Google Wifi platform.""" from datetime import datetime, timedelta import unittest -from unittest.mock import Mock, patch import requests_mock @@ -11,6 +10,7 @@ from homeassistant.const import STATE_UNKNOWN from homeassistant.setup import setup_component from homeassistant.util import dt as dt_util +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant NAME = "foo" diff --git a/tests/components/gpslogger/test_init.py b/tests/components/gpslogger/test_init.py index 9135f583d19..bfdc087193e 100644 --- a/tests/components/gpslogger/test_init.py +++ b/tests/components/gpslogger/test_init.py @@ -1,6 +1,4 @@ """The tests the for GPSLogger device tracker platform.""" -from unittest.mock import Mock, patch - import pytest from homeassistant import data_entry_flow @@ -16,6 +14,8 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch + HOME_LATITUDE = 37.239622 HOME_LONGITUDE = -115.815811 diff --git a/tests/components/graphite/test_init.py b/tests/components/graphite/test_init.py index 88be3723936..19b8c165f37 100644 --- a/tests/components/graphite/test_init.py +++ b/tests/components/graphite/test_init.py @@ -2,7 +2,6 @@ import socket import unittest from unittest import mock -from unittest.mock import patch import homeassistant.components.graphite as graphite from homeassistant.const import ( @@ -15,6 +14,7 @@ from homeassistant.const import ( import homeassistant.core as ha from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py index 467bd1ede95..c4d98ad37cc 100644 --- a/tests/components/group/test_init.py +++ b/tests/components/group/test_init.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from collections import OrderedDict import unittest -from unittest.mock import patch import homeassistant.components.group as group from homeassistant.const import ( @@ -17,6 +16,7 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component, setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant from tests.components.group import common diff --git a/tests/components/group/test_light.py b/tests/components/group/test_light.py index ed807ed57d9..70dab4472ed 100644 --- a/tests/components/group/test_light.py +++ b/tests/components/group/test_light.py @@ -1,6 +1,4 @@ """The tests for the Group Light platform.""" -from unittest.mock import MagicMock - from homeassistant.components.group import DOMAIN import homeassistant.components.group.light as group from homeassistant.components.light import ( @@ -31,6 +29,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import tests.async_mock +from tests.async_mock import MagicMock async def test_default_state(hass): diff --git a/tests/components/group/test_notify.py b/tests/components/group/test_notify.py index f029ec9d2fa..0925b318c9e 100644 --- a/tests/components/group/test_notify.py +++ b/tests/components/group/test_notify.py @@ -1,13 +1,13 @@ """The tests for the notify.group platform.""" import asyncio import unittest -from unittest.mock import MagicMock, patch import homeassistant.components.demo.notify as demo import homeassistant.components.group.notify as group import homeassistant.components.notify as notify from homeassistant.setup import setup_component +from tests.async_mock import MagicMock, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/group/test_reproduce_state.py b/tests/components/group/test_reproduce_state.py index 58bbc94876e..13422cd0826 100644 --- a/tests/components/group/test_reproduce_state.py +++ b/tests/components/group/test_reproduce_state.py @@ -1,11 +1,12 @@ """The tests for reproduction of state.""" from asyncio import Future -from unittest.mock import patch from homeassistant.components.group.reproduce_state import async_reproduce_states from homeassistant.core import Context, State +from tests.async_mock import patch + async def test_reproduce_group(hass): """Test reproduce_state with group.""" diff --git a/tests/components/hangouts/test_config_flow.py b/tests/components/hangouts/test_config_flow.py index 93f909d3bd4..9cdb5799951 100644 --- a/tests/components/hangouts/test_config_flow.py +++ b/tests/components/hangouts/test_config_flow.py @@ -1,11 +1,11 @@ """Tests for the Google Hangouts config flow.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components.hangouts import config_flow from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from tests.async_mock import patch + EMAIL = "test@test.com" PASSWORD = "1232456" diff --git a/tests/components/hassio/test_http.py b/tests/components/hassio/test_http.py index a5af24eb868..7386cf57d0c 100644 --- a/tests/components/hassio/test_http.py +++ b/tests/components/hassio/test_http.py @@ -1,9 +1,10 @@ """The tests for the hassio component.""" import asyncio -from unittest.mock import patch import pytest +from tests.async_mock import patch + async def test_forward_request(hassio_client, aioclient_mock): """Test fetching normal path.""" diff --git a/tests/components/hddtemp/test_sensor.py b/tests/components/hddtemp/test_sensor.py index cbaed220f11..4583079829a 100644 --- a/tests/components/hddtemp/test_sensor.py +++ b/tests/components/hddtemp/test_sensor.py @@ -1,11 +1,11 @@ """The tests for the hddtemp platform.""" import socket import unittest -from unittest.mock import patch from homeassistant.const import TEMP_CELSIUS from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant VALID_CONFIG_MINIMAL = {"sensor": {"platform": "hddtemp"}} diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index d399f5b67aa..fd2922e94fe 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -1,6 +1,5 @@ """The test for the here_travel_time sensor platform.""" import logging -from unittest.mock import patch import urllib import herepy @@ -43,6 +42,7 @@ from homeassistant.const import ATTR_ICON, EVENT_HOMEASSISTANT_START from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed, load_fixture DOMAIN = "sensor" diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index b2687b2bd50..29e43c8428e 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -2,13 +2,13 @@ # pylint: disable=protected-access,invalid-name from datetime import timedelta import unittest -from unittest.mock import patch, sentinel from homeassistant.components import history, recorder import homeassistant.core as ha from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch, sentinel from tests.common import ( get_test_home_assistant, init_recorder_component, diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index 588e0df81db..d9f489d20b4 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from datetime import datetime, timedelta import unittest -from unittest.mock import patch import pytest import pytz @@ -14,6 +13,7 @@ from homeassistant.helpers.template import Template from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 1371db87b3d..6234d425d8d 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -1,12 +1,11 @@ """Test Home Assistant scenes.""" -from unittest.mock import patch - import pytest import voluptuous as vol from homeassistant.components.homeassistant import scene as ha_scene from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import async_mock_service diff --git a/tests/components/homekit/common.py b/tests/components/homekit/common.py index 6453bbbc788..8f38332f112 100644 --- a/tests/components/homekit/common.py +++ b/tests/components/homekit/common.py @@ -1,5 +1,5 @@ """Collection of fixtures and functions for the HomeKit tests.""" -from unittest.mock import patch +from tests.async_mock import patch def patch_debounce(): diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 7093bebf9ab..d23bda67cee 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -1,12 +1,12 @@ """HomeKit session fixtures.""" -from unittest.mock import patch - 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.async_mock import patch + @pytest.fixture(scope="session") def hk_driver(): diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index e2fb79f56ce..55b7f764d76 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -3,7 +3,6 @@ This includes tests for all mock object types. """ from datetime import datetime, timedelta -from unittest.mock import Mock, patch import pytest @@ -43,6 +42,7 @@ from homeassistant.const import ( ) import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import async_mock_service diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index 286fe51535e..634c098b8f0 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -1,6 +1,4 @@ """Package to test the get_accessory method.""" -from unittest.mock import Mock, patch - import pytest import homeassistant.components.climate as climate @@ -31,6 +29,8 @@ from homeassistant.const import ( ) from homeassistant.core import State +from tests.async_mock import Mock, patch + def test_not_supported(caplog): """Test if none is returned if entity isn't supported.""" diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index e5bee83a0eb..7bdfa0b14bd 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,7 +1,6 @@ """Tests for the HomeKit component.""" import os from typing import Dict -from unittest.mock import ANY, Mock, patch import pytest from zeroconf import InterfaceChoice @@ -55,7 +54,7 @@ from homeassistant.util import json as json_util from .util import PATH_HOMEKIT, async_init_entry, async_init_integration -from tests.async_mock import AsyncMock +from tests.async_mock import ANY, AsyncMock, Mock, patch from tests.common import MockConfigEntry, mock_device_registry, mock_registry from tests.components.homekit.common import patch_debounce diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 4d2ace24eab..6d5b0f9841b 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -1,6 +1,5 @@ """Test different accessory types: Fans.""" from collections import namedtuple -from unittest.mock import Mock from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest @@ -33,6 +32,7 @@ from homeassistant.const import ( from homeassistant.core import CoreState from homeassistant.helpers import entity_registry +from tests.async_mock import Mock from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 82abed32c0e..8a303ede876 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -1,6 +1,5 @@ """Test different accessory types: Thermostats.""" from collections import namedtuple -from unittest.mock import patch from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest @@ -56,6 +55,7 @@ from homeassistant.const import ( from homeassistant.core import CoreState from homeassistant.helpers import entity_registry +from tests.async_mock import patch from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce diff --git a/tests/components/html5/test_notify.py b/tests/components/html5/test_notify.py index 4a62eb76c27..d3faa761435 100644 --- a/tests/components/html5/test_notify.py +++ b/tests/components/html5/test_notify.py @@ -1,6 +1,5 @@ """Test HTML5 notify platform.""" import json -from unittest.mock import MagicMock, mock_open, patch from aiohttp.hdrs import AUTHORIZATION @@ -9,6 +8,8 @@ from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, mock_open, patch + CONFIG_FILE = "file.conf" VAPID_CONF = { diff --git a/tests/components/http/test_auth.py b/tests/components/http/test_auth.py index 408bfd325c1..9282bf4587b 100644 --- a/tests/components/http/test_auth.py +++ b/tests/components/http/test_auth.py @@ -1,7 +1,6 @@ """The tests for the Home Assistant HTTP component.""" from datetime import timedelta from ipaddress import ip_network -from unittest.mock import patch from aiohttp import BasicAuth, web from aiohttp.web_exceptions import HTTPUnauthorized @@ -15,6 +14,8 @@ from homeassistant.setup import async_setup_component from . import HTTP_HEADER_HA_AUTH, mock_real_ip +from tests.async_mock import patch + API_PASSWORD = "test-password" # Don't add 127.0.0.1/::1 as trusted, as it may interfere with other test cases diff --git a/tests/components/http/test_ban.py b/tests/components/http/test_ban.py index d13581e12a2..702912dd9d0 100644 --- a/tests/components/http/test_ban.py +++ b/tests/components/http/test_ban.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from ipaddress import ip_address import os -from unittest.mock import Mock, mock_open from aiohttp import web from aiohttp.web_exceptions import HTTPUnauthorized @@ -24,7 +23,7 @@ from homeassistant.setup import async_setup_component from . import mock_real_ip -from tests.async_mock import patch +from tests.async_mock import Mock, mock_open, patch SUPERVISOR_IP = "1.2.3.4" BANNED_IPS = ["200.201.202.203", "100.64.0.2"] diff --git a/tests/components/http/test_cors.py b/tests/components/http/test_cors.py index 04447191fd5..191cdb0ba49 100644 --- a/tests/components/http/test_cors.py +++ b/tests/components/http/test_cors.py @@ -1,6 +1,5 @@ """Test cors for the HTTP component.""" from pathlib import Path -from unittest.mock import patch from aiohttp import web from aiohttp.hdrs import ( @@ -19,6 +18,8 @@ from homeassistant.setup import async_setup_component from . import HTTP_HEADER_HA_AUTH +from tests.async_mock import patch + TRUSTED_ORIGIN = "https://home-assistant.io" diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index 71b4b95cd8e..b0a14a31bc5 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -1,12 +1,12 @@ """Test data validator decorator.""" -from unittest.mock import Mock - from aiohttp import web import voluptuous as vol from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator +from tests.async_mock import Mock + async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 58e6d8824dd..651783ec47e 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -2,12 +2,13 @@ from ipaddress import ip_network import logging import unittest -from unittest.mock import patch import homeassistant.components.http as http from homeassistant.setup import async_setup_component from homeassistant.util.ssl import server_context_intermediate, server_context_modern +from tests.async_mock import patch + class TestView(http.HomeAssistantView): """Test the HTTP views.""" diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index b298fff6674..a6e4bdc12c8 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -1,6 +1,4 @@ """Tests for Home Assistant View.""" -from unittest.mock import Mock - from aiohttp.web_exceptions import ( HTTPBadRequest, HTTPInternalServerError, @@ -15,7 +13,7 @@ from homeassistant.components.http.view import ( ) from homeassistant.exceptions import ServiceNotFound, Unauthorized -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock @pytest.fixture diff --git a/tests/components/hue/conftest.py b/tests/components/hue/conftest.py index fa7c4ac473d..52ae43d65b4 100644 --- a/tests/components/hue/conftest.py +++ b/tests/components/hue/conftest.py @@ -1,6 +1,5 @@ """Test helpers for Hue.""" from collections import deque -from unittest.mock import Mock, patch from aiohue.groups import Groups from aiohue.lights import Lights @@ -11,6 +10,8 @@ from homeassistant import config_entries from homeassistant.components import hue from homeassistant.components.hue import sensor_base as hue_sensor_base +from tests.async_mock import Mock, patch + @pytest.fixture(autouse=True) def no_request_delay(): diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index b5ea2e4e0ea..4ba4ecb06a6 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for Philips Hue config flow.""" import asyncio -from unittest.mock import Mock from aiohttp import client_exceptions import aiohue @@ -12,7 +11,7 @@ from homeassistant import config_entries from homeassistant.components import ssdp from homeassistant.components.hue import config_flow, const -from tests.async_mock import AsyncMock, patch +from tests.async_mock import AsyncMock, Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/hue/test_light.py b/tests/components/hue/test_light.py index 85ef0052029..630e74cb1af 100644 --- a/tests/components/hue/test_light.py +++ b/tests/components/hue/test_light.py @@ -1,7 +1,6 @@ """Philips Hue lights platform tests.""" import asyncio import logging -from unittest.mock import Mock import aiohue @@ -10,6 +9,8 @@ from homeassistant.components import hue from homeassistant.components.hue import light as hue_light from homeassistant.util import color +from tests.async_mock import Mock + _LOGGER = logging.getLogger(__name__) HUE_LIGHT_NS = "homeassistant.components.light.hue." diff --git a/tests/components/hue/test_sensor_base.py b/tests/components/hue/test_sensor_base.py index 576bc365d50..e50ac71c03a 100644 --- a/tests/components/hue/test_sensor_base.py +++ b/tests/components/hue/test_sensor_base.py @@ -1,7 +1,6 @@ """Philips Hue sensors platform tests.""" import asyncio import logging -from unittest.mock import Mock import aiohue @@ -9,6 +8,8 @@ from homeassistant.components.hue.hue_event import CONF_HUE_EVENT from .conftest import create_mock_bridge, setup_bridge_for_sensors as setup_bridge +from tests.async_mock import Mock + _LOGGER = logging.getLogger(__name__) PRESENCE_SENSOR_1_PRESENT = { diff --git a/tests/components/icloud/conftest.py b/tests/components/icloud/conftest.py index 2230cc2ea32..2ed9006cdb6 100644 --- a/tests/components/icloud/conftest.py +++ b/tests/components/icloud/conftest.py @@ -1,8 +1,8 @@ """Configure iCloud tests.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + @pytest.fixture(name="icloud_bypass_setup", autouse=True) def icloud_bypass_setup_fixture(): diff --git a/tests/components/icloud/test_config_flow.py b/tests/components/icloud/test_config_flow.py index 4bce35d0a63..3123ead4eeb 100644 --- a/tests/components/icloud/test_config_flow.py +++ b/tests/components/icloud/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the iCloud config flow.""" -from unittest.mock import MagicMock, Mock, patch - from pyicloud.exceptions import PyiCloudFailedLoginException import pytest @@ -22,6 +20,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import MagicMock, Mock, patch from tests.common import MockConfigEntry USERNAME = "username@me.com" diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py index d10df2492d4..e5fd8841a2b 100644 --- a/tests/components/ifttt/test_init.py +++ b/tests/components/ifttt/test_init.py @@ -1,10 +1,10 @@ """Test the init file of IFTTT.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components import ifttt from homeassistant.core import callback +from tests.async_mock import patch + async def test_config_flow_registers_webhook(hass, aiohttp_client): """Test setting up IFTTT and sending webhook.""" diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index c2bd357ecc8..8b9852ea234 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -1,6 +1,5 @@ """The tests for the IGN Sismologia (Earthquakes) Feed platform.""" import datetime -from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -29,6 +28,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed CONFIG = {geo_location.DOMAIN: [{"platform": "ign_sismologia", CONF_RADIUS: 200}]} diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index a2a2ec2d7a6..cc708db75db 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -1,6 +1,4 @@ """The tests for the image_processing component.""" -from unittest.mock import PropertyMock - import homeassistant.components.http as http import homeassistant.components.image_processing as ip from homeassistant.const import ATTR_ENTITY_PICTURE @@ -8,7 +6,7 @@ from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import setup_component -from tests.async_mock import patch +from tests.async_mock import PropertyMock, patch from tests.common import ( assert_setup_component, get_test_home_assistant, diff --git a/tests/components/input_boolean/test_init.py b/tests/components/input_boolean/test_init.py index c0bdad3eacd..1e3150e687a 100644 --- a/tests/components/input_boolean/test_init.py +++ b/tests/components/input_boolean/test_init.py @@ -1,7 +1,6 @@ """The tests for the input_boolean component.""" # pylint: disable=protected-access import logging -from unittest.mock import patch import pytest @@ -23,6 +22,7 @@ from homeassistant.core import Context, CoreState, State from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import mock_component, mock_restore_cache _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/input_datetime/test_init.py b/tests/components/input_datetime/test_init.py index afee32702c4..0eb4d748563 100644 --- a/tests/components/input_datetime/test_init.py +++ b/tests/components/input_datetime/test_init.py @@ -1,7 +1,6 @@ """Tests for the Input slider component.""" # pylint: disable=protected-access import datetime -from unittest.mock import patch import pytest import voluptuous as vol @@ -27,6 +26,7 @@ from homeassistant.exceptions import Unauthorized from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import mock_restore_cache INITIAL_DATE = "2020-01-10" diff --git a/tests/components/input_number/test_init.py b/tests/components/input_number/test_init.py index 28b9d27d23f..8971439de74 100644 --- a/tests/components/input_number/test_init.py +++ b/tests/components/input_number/test_init.py @@ -1,7 +1,4 @@ """The tests for the Input number component.""" -# pylint: disable=protected-access -from unittest.mock import patch - import pytest import voluptuous as vol @@ -24,6 +21,8 @@ from homeassistant.exceptions import Unauthorized from homeassistant.helpers import entity_registry from homeassistant.setup import async_setup_component +# pylint: disable=protected-access +from tests.async_mock import patch from tests.common import mock_restore_cache diff --git a/tests/components/input_select/test_init.py b/tests/components/input_select/test_init.py index 5c470ca5bfc..6f83de340f3 100644 --- a/tests/components/input_select/test_init.py +++ b/tests/components/input_select/test_init.py @@ -1,7 +1,4 @@ """The tests for the Input select component.""" -# pylint: disable=protected-access -from unittest.mock import patch - import pytest from homeassistant.components.input_select import ( @@ -28,6 +25,8 @@ from homeassistant.helpers import entity_registry from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component +# pylint: disable=protected-access +from tests.async_mock import patch from tests.common import mock_restore_cache diff --git a/tests/components/input_text/test_init.py b/tests/components/input_text/test_init.py index cc226dc1d87..ed89ccd7087 100644 --- a/tests/components/input_text/test_init.py +++ b/tests/components/input_text/test_init.py @@ -1,7 +1,4 @@ """The tests for the Input text component.""" -# pylint: disable=protected-access -from unittest.mock import patch - import pytest from homeassistant.components.input_text import ( @@ -29,6 +26,8 @@ from homeassistant.helpers import entity_registry from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component +# pylint: disable=protected-access +from tests.async_mock import patch from tests.common import mock_restore_cache TEST_VAL_MIN = 2 diff --git a/tests/components/integration/test_sensor.py b/tests/components/integration/test_sensor.py index 3afa5c14c22..c36a718ffb4 100644 --- a/tests/components/integration/test_sensor.py +++ b/tests/components/integration/test_sensor.py @@ -1,11 +1,12 @@ """The tests for the integration sensor platform.""" from datetime import timedelta -from unittest.mock import patch from homeassistant.const import ENERGY_KILO_WATT_HOUR, POWER_WATT, TIME_SECONDS from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch + async def test_state(hass): """Test integration sensor state.""" diff --git a/tests/components/islamic_prayer_times/test_config_flow.py b/tests/components/islamic_prayer_times/test_config_flow.py index a56178e5225..204075ecb8c 100644 --- a/tests/components/islamic_prayer_times/test_config_flow.py +++ b/tests/components/islamic_prayer_times/test_config_flow.py @@ -1,12 +1,11 @@ """Tests for Islamic Prayer Times config flow.""" -from unittest.mock import patch - import pytest from homeassistant import data_entry_flow from homeassistant.components import islamic_prayer_times from homeassistant.components.islamic_prayer_times.const import CONF_CALC_METHOD, DOMAIN +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/islamic_prayer_times/test_init.py b/tests/components/islamic_prayer_times/test_init.py index e91e83b315e..9fb9333e045 100644 --- a/tests/components/islamic_prayer_times/test_init.py +++ b/tests/components/islamic_prayer_times/test_init.py @@ -1,7 +1,6 @@ """Tests for Islamic Prayer Times init.""" from datetime import timedelta -from unittest.mock import patch from prayer_times_calculator.exceptions import InvalidResponseError @@ -17,6 +16,7 @@ from . import ( PRAYER_TIMES_TIMESTAMPS, ) +from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed diff --git a/tests/components/islamic_prayer_times/test_sensor.py b/tests/components/islamic_prayer_times/test_sensor.py index 0579664ae7b..147d7e6ef1d 100644 --- a/tests/components/islamic_prayer_times/test_sensor.py +++ b/tests/components/islamic_prayer_times/test_sensor.py @@ -1,10 +1,9 @@ """The tests for the Islamic prayer times sensor platform.""" -from unittest.mock import patch - from homeassistant.components import islamic_prayer_times from . import NOW, PRAYER_TIMES, PRAYER_TIMES_TIMESTAMPS +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/jewish_calendar/__init__.py b/tests/components/jewish_calendar/__init__.py index 592086461d3..8e18579b197 100644 --- a/tests/components/jewish_calendar/__init__.py +++ b/tests/components/jewish_calendar/__init__.py @@ -2,11 +2,12 @@ from collections import namedtuple from contextlib import contextmanager from datetime import datetime -from unittest.mock import patch from homeassistant.components import jewish_calendar import homeassistant.util.dt as dt_util +from tests.async_mock import patch + _LatLng = namedtuple("_LatLng", ["lat", "lng"]) NYC_LATLNG = _LatLng(40.7128, -74.0060) diff --git a/tests/components/kira/test_init.py b/tests/components/kira/test_init.py index e5056235127..8656ac23264 100644 --- a/tests/components/kira/test_init.py +++ b/tests/components/kira/test_init.py @@ -4,11 +4,11 @@ import os import shutil import tempfile import unittest -from unittest.mock import MagicMock, patch import homeassistant.components.kira as kira from homeassistant.setup import setup_component +from tests.async_mock import MagicMock, patch from tests.common import get_test_home_assistant TEST_CONFIG = { diff --git a/tests/components/kira/test_remote.py b/tests/components/kira/test_remote.py index d7718ad33f0..b1ac7ea12fc 100644 --- a/tests/components/kira/test_remote.py +++ b/tests/components/kira/test_remote.py @@ -1,9 +1,9 @@ """The tests for Kira sensor platform.""" import unittest -from unittest.mock import MagicMock from homeassistant.components.kira import remote as kira +from tests.async_mock import MagicMock from tests.common import get_test_home_assistant SERVICE_SEND_COMMAND = "send_command" diff --git a/tests/components/kira/test_sensor.py b/tests/components/kira/test_sensor.py index 55e6d5453c7..2fae36dc670 100644 --- a/tests/components/kira/test_sensor.py +++ b/tests/components/kira/test_sensor.py @@ -1,9 +1,9 @@ """The tests for Kira sensor platform.""" import unittest -from unittest.mock import MagicMock from homeassistant.components.kira import sensor as kira +from tests.async_mock import MagicMock from tests.common import get_test_home_assistant TEST_CONFIG = {kira.DOMAIN: {"sensors": [{"host": "127.0.0.1", "port": 17324}]}} diff --git a/tests/components/lastfm/test_sensor.py b/tests/components/lastfm/test_sensor.py index c2ce8e947dd..7ae70a6f152 100644 --- a/tests/components/lastfm/test_sensor.py +++ b/tests/components/lastfm/test_sensor.py @@ -1,6 +1,4 @@ """Tests for the lastfm sensor.""" -from unittest.mock import patch - from pylast import Track import pytest @@ -8,6 +6,8 @@ from homeassistant.components import sensor from homeassistant.components.lastfm.sensor import STATE_NOT_SCROBBLING from homeassistant.setup import async_setup_component +from tests.async_mock import patch + class MockUser: """Mock user object for pylast.""" diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 24645a32611..998ef7851c1 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -1,6 +1,5 @@ """The test for light device automation.""" from datetime import timedelta -from unittest.mock import patch import pytest @@ -11,6 +10,7 @@ from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_get_device_automation_capabilities, diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index e53949ef518..87452d62893 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -3,7 +3,6 @@ from io import StringIO import os import unittest -import unittest.mock as mock import pytest @@ -21,6 +20,7 @@ from homeassistant.const import ( from homeassistant.exceptions import Unauthorized from homeassistant.setup import async_setup_component, setup_component +import tests.async_mock as mock from tests.common import get_test_home_assistant, mock_service, mock_storage from tests.components.light import common diff --git a/tests/components/linky/conftest.py b/tests/components/linky/conftest.py index f77f01a4ae7..93e3ff78d2b 100644 --- a/tests/components/linky/conftest.py +++ b/tests/components/linky/conftest.py @@ -1,8 +1,8 @@ """Linky generic test utils.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + @pytest.fixture(autouse=True) def patch_fakeuseragent(): diff --git a/tests/components/linky/test_config_flow.py b/tests/components/linky/test_config_flow.py index 8278a77d4d0..f39f0da7d99 100644 --- a/tests/components/linky/test_config_flow.py +++ b/tests/components/linky/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the Linky config flow.""" -from unittest.mock import Mock, patch - from pylinky.exceptions import ( PyLinkyAccessException, PyLinkyEnedisException, @@ -15,6 +13,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry USERNAME = "username@hotmail.fr" diff --git a/tests/components/locative/test_init.py b/tests/components/locative/test_init.py index 63ef2e9f5f8..05b6a84cc0c 100644 --- a/tests/components/locative/test_init.py +++ b/tests/components/locative/test_init.py @@ -1,6 +1,4 @@ """The tests the for Locative device tracker platform.""" -from unittest.mock import Mock, patch - import pytest from homeassistant import data_entry_flow @@ -11,6 +9,8 @@ from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch + # pylint: disable=redefined-outer-name diff --git a/tests/components/lovelace/test_dashboard.py b/tests/components/lovelace/test_dashboard.py index 8509ad37fcd..fb8c909eac5 100644 --- a/tests/components/lovelace/test_dashboard.py +++ b/tests/components/lovelace/test_dashboard.py @@ -1,12 +1,11 @@ """Test the Lovelace initialization.""" -from unittest.mock import patch - import pytest from homeassistant.components import frontend from homeassistant.components.lovelace import const, dashboard from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import ( assert_setup_component, async_capture_events, diff --git a/tests/components/luftdaten/test_init.py b/tests/components/luftdaten/test_init.py index ebe5f73669e..a8ea57cbb6b 100644 --- a/tests/components/luftdaten/test_init.py +++ b/tests/components/luftdaten/test_init.py @@ -1,11 +1,11 @@ """Test the Luftdaten component setup.""" -from unittest.mock import patch - from homeassistant.components import luftdaten from homeassistant.components.luftdaten.const import CONF_SENSOR_ID, DOMAIN from homeassistant.const import CONF_SCAN_INTERVAL, CONF_SHOW_ON_MAP from homeassistant.setup import async_setup_component +from tests.async_mock import patch + async def test_config_with_sensor_passed_to_config_entry(hass): """Test that configured options for a sensor are loaded.""" diff --git a/tests/components/mailgun/test_init.py b/tests/components/mailgun/test_init.py index 7ed67dcc0d2..8b98772dfcc 100644 --- a/tests/components/mailgun/test_init.py +++ b/tests/components/mailgun/test_init.py @@ -1,7 +1,6 @@ """Test the init file of Mailgun.""" import hashlib import hmac -from unittest.mock import Mock import pytest @@ -11,6 +10,8 @@ from homeassistant.const import CONF_API_KEY, CONF_DOMAIN from homeassistant.core import callback from homeassistant.setup import async_setup_component +from tests.async_mock import Mock + API_KEY = "abc123" diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index 22dceec312b..d3e26597d84 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -1,6 +1,5 @@ """The tests for the manual Alarm Control Panel component.""" from datetime import timedelta -from unittest.mock import MagicMock, patch from homeassistant.components import alarm_control_panel from homeassistant.components.demo import alarm_control_panel as demo @@ -18,6 +17,7 @@ from homeassistant.core import CoreState, State from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, patch from tests.common import async_fire_time_changed, mock_component, mock_restore_cache from tests.components.alarm_control_panel import common diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index 1ac6bca91c4..bff3818af56 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -1,7 +1,6 @@ """The tests for the manual_mqtt Alarm Control Panel component.""" from datetime import timedelta import unittest -from unittest.mock import Mock, patch from homeassistant.components import alarm_control_panel from homeassistant.const import ( @@ -15,6 +14,7 @@ from homeassistant.const import ( from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import Mock, patch from tests.common import ( assert_setup_component, fire_mqtt_message, diff --git a/tests/components/melissa/test_climate.py b/tests/components/melissa/test_climate.py index 00c565aca8c..874cc29ab7a 100644 --- a/tests/components/melissa/test_climate.py +++ b/tests/components/melissa/test_climate.py @@ -1,6 +1,5 @@ """Test for Melissa climate component.""" import json -from unittest.mock import Mock, patch from homeassistant.components.climate.const import ( HVAC_MODE_COOL, @@ -16,7 +15,7 @@ from homeassistant.components.melissa import DATA_MELISSA, climate as melissa from homeassistant.components.melissa.climate import MelissaClimate from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock, patch from tests.common import load_fixture _SERIAL = "12345678" diff --git a/tests/components/meteo_france/conftest.py b/tests/components/meteo_france/conftest.py index 088587ab2c2..75c294775ed 100644 --- a/tests/components/meteo_france/conftest.py +++ b/tests/components/meteo_france/conftest.py @@ -1,8 +1,8 @@ """Meteo-France generic test utils.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + @pytest.fixture(autouse=True) def patch_requests(): diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index f9ead2c1ef3..d4381073209 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the Meteo-France config flow.""" -from unittest.mock import patch - from meteofrance.client import meteofranceError import pytest @@ -8,6 +6,7 @@ from homeassistant import data_entry_flow from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from tests.async_mock import patch from tests.common import MockConfigEntry CITY_1_POSTAL = "74220" diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index 05f175fc191..ff9c7fa7182 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -1,6 +1,5 @@ """The tests for the mFi sensor platform.""" import unittest -import unittest.mock as mock from mficlient.client import FailedToLogin import requests @@ -10,6 +9,7 @@ import homeassistant.components.sensor as sensor from homeassistant.const import TEMP_CELSIUS from homeassistant.setup import setup_component +import tests.async_mock as mock from tests.common import get_test_home_assistant diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index 42469b1b5ac..414e0b8b50b 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -1,11 +1,11 @@ """The tests for the mFi switch platform.""" import unittest -import unittest.mock as mock import homeassistant.components.mfi.switch as mfi import homeassistant.components.switch as switch from homeassistant.setup import setup_component +import tests.async_mock as mock from tests.common import get_test_home_assistant diff --git a/tests/components/mhz19/test_sensor.py b/tests/components/mhz19/test_sensor.py index 598144f5a25..05d462f02a0 100644 --- a/tests/components/mhz19/test_sensor.py +++ b/tests/components/mhz19/test_sensor.py @@ -1,6 +1,5 @@ """Tests for MH-Z19 sensor.""" import unittest -from unittest.mock import DEFAULT, Mock, patch import homeassistant.components.mhz19.sensor as mhz19 from homeassistant.components.sensor import DOMAIN @@ -11,6 +10,7 @@ from homeassistant.const import ( ) from homeassistant.setup import setup_component +from tests.async_mock import DEFAULT, Mock, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/mikrotik/test_config_flow.py b/tests/components/mikrotik/test_config_flow.py index 37dbfad4d35..b47bb941af4 100644 --- a/tests/components/mikrotik/test_config_flow.py +++ b/tests/components/mikrotik/test_config_flow.py @@ -1,6 +1,5 @@ """Test Mikrotik setup process.""" from datetime import timedelta -from unittest.mock import patch import librouteros import pytest @@ -16,6 +15,7 @@ from homeassistant.const import ( CONF_VERIFY_SSL, ) +from tests.async_mock import patch from tests.common import MockConfigEntry DEMO_USER_INPUT = { diff --git a/tests/components/minio/test_minio.py b/tests/components/minio/test_minio.py index a1f3e107152..88f7418d2d4 100644 --- a/tests/components/minio/test_minio.py +++ b/tests/components/minio/test_minio.py @@ -1,7 +1,6 @@ """Tests for Minio Hass related code.""" import asyncio import json -from unittest.mock import MagicMock import pytest @@ -19,7 +18,7 @@ from homeassistant.components.minio import ( from homeassistant.core import callback from homeassistant.setup import async_setup_component -from tests.async_mock import call, patch +from tests.async_mock import MagicMock, call, patch from tests.components.minio.common import TEST_EVENT diff --git a/tests/components/mochad/test_light.py b/tests/components/mochad/test_light.py index 631c5b40734..2dd385f0253 100644 --- a/tests/components/mochad/test_light.py +++ b/tests/components/mochad/test_light.py @@ -1,6 +1,5 @@ """The tests for the mochad light platform.""" import unittest -import unittest.mock as mock import pytest @@ -8,6 +7,7 @@ from homeassistant.components import light from homeassistant.components.mochad import light as mochad from homeassistant.setup import setup_component +import tests.async_mock as mock from tests.common import get_test_home_assistant diff --git a/tests/components/mochad/test_switch.py b/tests/components/mochad/test_switch.py index aa6ce354a32..699edfe899c 100644 --- a/tests/components/mochad/test_switch.py +++ b/tests/components/mochad/test_switch.py @@ -1,6 +1,5 @@ """The tests for the mochad switch platform.""" import unittest -import unittest.mock as mock import pytest @@ -8,6 +7,7 @@ from homeassistant.components import switch from homeassistant.components.mochad import switch as mochad from homeassistant.setup import setup_component +import tests.async_mock as mock from tests.common import get_test_home_assistant diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index 1e19d0a4d83..89d5a8f798c 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -1,11 +1,11 @@ """The test for the moon sensor platform.""" from datetime import datetime import unittest -from unittest.mock import patch from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import get_test_home_assistant DAY1 = datetime(2017, 1, 1, 1, tzinfo=dt_util.UTC) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 11fb073f57a..7c07abb8d35 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -2,7 +2,6 @@ import copy from datetime import datetime, timedelta import json -from unittest.mock import patch from homeassistant.components import binary_sensor, mqtt from homeassistant.components.mqtt.discovery import async_start @@ -38,6 +37,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_fire_mqtt_message, diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 1d485c4be13..9ae13a426b9 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -1,7 +1,6 @@ """The tests for the mqtt climate component.""" import copy import json -import unittest import pytest import voluptuous as vol @@ -47,6 +46,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import call from tests.common import async_fire_mqtt_message, async_setup_component from tests.components.climate import common @@ -180,10 +180,7 @@ async def test_set_operation_with_power_command(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) assert state.state == "cool" mqtt_mock.async_publish.assert_has_calls( - [ - unittest.mock.call("power-command", "ON", 0, False), - unittest.mock.call("mode-topic", "cool", 0, False), - ] + [call("power-command", "ON", 0, False), call("mode-topic", "cool", 0, False)] ) mqtt_mock.async_publish.reset_mock() @@ -191,10 +188,7 @@ async def test_set_operation_with_power_command(hass, mqtt_mock): state = hass.states.get(ENTITY_CLIMATE) assert state.state == "off" mqtt_mock.async_publish.assert_has_calls( - [ - unittest.mock.call("power-command", "OFF", 0, False), - unittest.mock.call("mode-topic", "off", 0, False), - ] + [call("power-command", "OFF", 0, False), call("mode-topic", "off", 0, False)] ) mqtt_mock.async_publish.reset_mock() @@ -322,10 +316,7 @@ async def test_set_target_temperature(hass, mqtt_mock): assert state.state == "cool" assert state.attributes.get("temperature") == 21 mqtt_mock.async_publish.assert_has_calls( - [ - unittest.mock.call("mode-topic", "cool", 0, False), - unittest.mock.call("temperature-topic", 21, 0, False), - ] + [call("mode-topic", "cool", 0, False), call("temperature-topic", 21, 0, False)] ) mqtt_mock.async_publish.reset_mock() @@ -465,10 +456,7 @@ async def test_set_away_mode(hass, mqtt_mock): await common.async_set_preset_mode(hass, "away", ENTITY_CLIMATE) mqtt_mock.async_publish.assert_has_calls( - [ - unittest.mock.call("hold-topic", "off", 0, False), - unittest.mock.call("away-mode-topic", "AN", 0, False), - ] + [call("hold-topic", "off", 0, False), call("away-mode-topic", "AN", 0, False)] ) state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") == "away" diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 949d77c244d..4bc3bc1a937 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -3,13 +3,13 @@ import copy from datetime import datetime import json from unittest import mock -from unittest.mock import ANY from homeassistant.components import mqtt from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt.discovery import async_start from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE +from tests.async_mock import ANY from tests.common import ( MockConfigEntry, async_fire_mqtt_message, diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 9d8ede4f516..8c75d77efb8 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1,7 +1,6 @@ """The tests for the MQTT discovery.""" from pathlib import Path import re -from unittest.mock import patch import pytest @@ -13,7 +12,7 @@ from homeassistant.components.mqtt.abbreviations import ( from homeassistant.components.mqtt.discovery import ALREADY_DISCOVERED, async_start from homeassistant.const import STATE_OFF, STATE_ON -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch from tests.common import ( MockConfigEntry, async_fire_mqtt_message, diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 34d3c33f8d7..1acc7656a8b 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the MQTT sensor platform.""" from datetime import datetime, timedelta import json -from unittest.mock import patch from homeassistant.components import mqtt from homeassistant.components.mqtt.discovery import async_start @@ -37,6 +36,7 @@ from .test_common import ( help_test_update_with_json_attrs_not_dict, ) +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_fire_mqtt_message, diff --git a/tests/components/mqtt_eventstream/test_init.py b/tests/components/mqtt_eventstream/test_init.py index eeeab823744..8050535eed4 100644 --- a/tests/components/mqtt_eventstream/test_init.py +++ b/tests/components/mqtt_eventstream/test_init.py @@ -1,6 +1,5 @@ """The tests for the MQTT eventstream component.""" import json -from unittest.mock import ANY, patch import pytest @@ -11,6 +10,7 @@ from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import ANY, patch from tests.common import ( fire_mqtt_message, fire_time_changed, diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index b3155e563f4..f11786951f6 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the MQTT room presence sensor.""" import datetime import json -from unittest.mock import patch from homeassistant.components.mqtt import CONF_QOS, CONF_STATE_TOPIC, DEFAULT_QOS import homeassistant.components.sensor as sensor @@ -9,6 +8,7 @@ from homeassistant.const import CONF_NAME, CONF_PLATFORM from homeassistant.setup import async_setup_component from homeassistant.util import dt +from tests.async_mock import patch from tests.common import async_fire_mqtt_message, async_mock_mqtt_component DEVICE_ID = "123TESTMAC" diff --git a/tests/components/mqtt_statestream/test_init.py b/tests/components/mqtt_statestream/test_init.py index 6be413294f1..aa9ef0d5de8 100644 --- a/tests/components/mqtt_statestream/test_init.py +++ b/tests/components/mqtt_statestream/test_init.py @@ -1,12 +1,11 @@ """The tests for the MQTT statestream component.""" -from unittest.mock import ANY, call, patch - import pytest import homeassistant.components.mqtt_statestream as statestream from homeassistant.core import State from homeassistant.setup import setup_component +from tests.async_mock import ANY, call, patch from tests.common import ( get_test_home_assistant, mock_mqtt_component, diff --git a/tests/components/neato/test_config_flow.py b/tests/components/neato/test_config_flow.py index 59db79c1052..be69e0853ad 100644 --- a/tests/components/neato/test_config_flow.py +++ b/tests/components/neato/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the Neato config flow.""" -from unittest.mock import patch - from pybotvac.exceptions import NeatoLoginException, NeatoRobotException import pytest @@ -9,6 +7,7 @@ from homeassistant.components.neato import config_flow from homeassistant.components.neato.const import CONF_VENDOR, NEATO_DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry USERNAME = "myUsername" diff --git a/tests/components/neato/test_init.py b/tests/components/neato/test_init.py index 8fa6ad05945..182ef98e529 100644 --- a/tests/components/neato/test_init.py +++ b/tests/components/neato/test_init.py @@ -1,6 +1,4 @@ """Tests for the Neato init file.""" -from unittest.mock import patch - from pybotvac.exceptions import NeatoLoginException import pytest @@ -8,6 +6,7 @@ from homeassistant.components.neato.const import CONF_VENDOR, NEATO_DOMAIN from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry USERNAME = "myUsername" diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index bee9db445e2..dd709618ec0 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -1,12 +1,12 @@ """The tests for the nexbus sensor component.""" from copy import deepcopy -from unittest.mock import patch import pytest import homeassistant.components.nextbus.sensor as nextbus import homeassistant.components.sensor as sensor +from tests.async_mock import patch from tests.common import assert_setup_component, async_setup_component VALID_AGENCY = "sf-muni" diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 11a3d469a59..2d348204dcc 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -1,10 +1,10 @@ """The tests for the NSW Fuel Station sensor platform.""" import unittest -from unittest.mock import patch from homeassistant.components import sensor from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant VALID_CONFIG = { diff --git a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py index a5167104d48..584616967c4 100644 --- a/tests/components/nsw_rural_fire_service_feed/test_geo_location.py +++ b/tests/components/nsw_rural_fire_service_feed/test_geo_location.py @@ -1,6 +1,5 @@ """The tests for the NSW Rural Fire Service Feeds platform.""" import datetime -from unittest.mock import ANY from aio_geojson_nsw_rfs_incidents import NswRuralFireServiceIncidentsFeed @@ -36,7 +35,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from tests.async_mock import MagicMock, call, patch +from tests.async_mock import ANY, MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed CONFIG = { diff --git a/tests/components/nuheat/test_init.py b/tests/components/nuheat/test_init.py index 01128610462..4a7a8673230 100644 --- a/tests/components/nuheat/test_init.py +++ b/tests/components/nuheat/test_init.py @@ -1,11 +1,11 @@ """NuHeat component tests.""" -from unittest.mock import patch - from homeassistant.components.nuheat.const import DOMAIN from homeassistant.setup import async_setup_component from .mocks import _get_mock_nuheat +from tests.async_mock import patch + VALID_CONFIG = { "nuheat": {"username": "warm", "password": "feet", "devices": "thermostat123"} } diff --git a/tests/components/nws/conftest.py b/tests/components/nws/conftest.py index ac8428ddf48..74f84eb200c 100644 --- a/tests/components/nws/conftest.py +++ b/tests/components/nws/conftest.py @@ -1,9 +1,7 @@ """Fixtures for National Weather Service tests.""" -from unittest.mock import patch - import pytest -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch from tests.components.nws.const import DEFAULT_FORECAST, DEFAULT_OBSERVATION diff --git a/tests/components/onboarding/test_views.py b/tests/components/onboarding/test_views.py index 91ed8d7ae5c..7deda0e7edc 100644 --- a/tests/components/onboarding/test_views.py +++ b/tests/components/onboarding/test_views.py @@ -1,6 +1,5 @@ """Test the onboarding views.""" import asyncio -from unittest.mock import patch import pytest @@ -11,6 +10,7 @@ from homeassistant.setup import async_setup_component from . import mock_storage +from tests.async_mock import patch from tests.common import CLIENT_ID, register_auth_provider from tests.components.met.conftest import mock_weather # noqa: F401 diff --git a/tests/components/openerz/test_sensor.py b/tests/components/openerz/test_sensor.py index 24a0f0610af..e616ea4fe4e 100644 --- a/tests/components/openerz/test_sensor.py +++ b/tests/components/openerz/test_sensor.py @@ -1,9 +1,9 @@ """Tests for OpenERZ component.""" -from unittest.mock import MagicMock, patch - from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + MOCK_CONFIG = { "sensor": { "platform": "openerz", diff --git a/tests/components/owntracks/test_helper.py b/tests/components/owntracks/test_helper.py index 2c06ac0c4e7..6d5139caa14 100644 --- a/tests/components/owntracks/test_helper.py +++ b/tests/components/owntracks/test_helper.py @@ -1,10 +1,10 @@ """Test the owntracks_http platform.""" -from unittest.mock import patch - import pytest from homeassistant.components.owntracks import helper +from tests.async_mock import patch + @pytest.fixture(name="nacl_imported") def mock_nacl_imported(): diff --git a/tests/components/panasonic_viera/test_config_flow.py b/tests/components/panasonic_viera/test_config_flow.py index cc7c3f58e82..0e7731dbdc0 100644 --- a/tests/components/panasonic_viera/test_config_flow.py +++ b/tests/components/panasonic_viera/test_config_flow.py @@ -1,6 +1,4 @@ """Test the Panasonic Viera config flow.""" -from unittest.mock import Mock - from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED, SOAPError import pytest @@ -19,7 +17,7 @@ from homeassistant.components.panasonic_viera.const import ( ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT -from tests.async_mock import patch +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry diff --git a/tests/components/panasonic_viera/test_init.py b/tests/components/panasonic_viera/test_init.py index 3e02ac3703a..263f2def9af 100644 --- a/tests/components/panasonic_viera/test_init.py +++ b/tests/components/panasonic_viera/test_init.py @@ -1,6 +1,4 @@ """Test the Panasonic Viera setup process.""" -from unittest.mock import Mock - from asynctest import patch from homeassistant.components.panasonic_viera.const import ( @@ -15,6 +13,7 @@ from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.setup import async_setup_component +from tests.async_mock import Mock from tests.common import MockConfigEntry MOCK_CONFIG_DATA = { diff --git a/tests/components/panel_custom/test_init.py b/tests/components/panel_custom/test_init.py index 5f7161089f6..c2abd673065 100644 --- a/tests/components/panel_custom/test_init.py +++ b/tests/components/panel_custom/test_init.py @@ -1,9 +1,9 @@ """The tests for the panel_custom component.""" -from unittest.mock import Mock, patch - from homeassistant import setup from homeassistant.components import frontend +from tests.async_mock import Mock, patch + async def test_webcomponent_custom_path_not_found(hass): """Test if a web component is found in config panels dir.""" diff --git a/tests/components/pi_hole/test_init.py b/tests/components/pi_hole/test_init.py index 236e8eadde8..3ff16001d86 100644 --- a/tests/components/pi_hole/test_init.py +++ b/tests/components/pi_hole/test_init.py @@ -1,10 +1,8 @@ """Test pi_hole component.""" -from unittest.mock import patch - from homeassistant.components import pi_hole -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch from tests.common import async_setup_component ZERO_DATA = { diff --git a/tests/components/pilight/test_init.py b/tests/components/pilight/test_init.py index d9b4f4859bb..53b1ec3a94d 100644 --- a/tests/components/pilight/test_init.py +++ b/tests/components/pilight/test_init.py @@ -3,7 +3,6 @@ from datetime import timedelta import logging import socket import unittest -from unittest.mock import patch import pytest @@ -12,6 +11,7 @@ from homeassistant.components import pilight from homeassistant.setup import setup_component from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant _LOGGER = logging.getLogger(__name__) @@ -109,7 +109,7 @@ class TestPilight(unittest.TestCase): @patch("pilight.pilight.Client", PilightDaemonSim) @patch("homeassistant.core._LOGGER.error") - @patch("tests.components.test_pilight._LOGGER.error") + @patch("homeassistant.components.pilight._LOGGER.error") def test_send_code_no_protocol(self, mock_pilight_error, mock_error): """Try to send data without protocol information, should give error.""" with assert_setup_component(4): @@ -127,7 +127,7 @@ class TestPilight(unittest.TestCase): assert "required key not provided @ data['protocol']" in str(error_log_call) @patch("pilight.pilight.Client", PilightDaemonSim) - @patch("tests.components.test_pilight._LOGGER.error") + @patch("homeassistant.components.pilight._LOGGER.error") def test_send_code(self, mock_pilight_error): """Try to send proper data.""" with assert_setup_component(4): @@ -167,7 +167,7 @@ class TestPilight(unittest.TestCase): assert "Pilight send failed" in str(error_log_call) @patch("pilight.pilight.Client", PilightDaemonSim) - @patch("tests.components.test_pilight._LOGGER.error") + @patch("homeassistant.components.pilight._LOGGER.error") def test_send_code_delay(self, mock_pilight_error): """Try to send proper data with delay afterwards.""" with assert_setup_component(4): @@ -207,7 +207,7 @@ class TestPilight(unittest.TestCase): assert str(service_data2) in str(error_log_call) @patch("pilight.pilight.Client", PilightDaemonSim) - @patch("tests.components.test_pilight._LOGGER.error") + @patch("homeassistant.components.pilight._LOGGER.error") def test_start_stop(self, mock_pilight_error): """Check correct startup and stop of pilight daemon.""" with assert_setup_component(4): diff --git a/tests/components/ptvsd/test_ptvsd.py b/tests/components/ptvsd/test_ptvsd.py index 9df686cfcbd..93e1bb540db 100644 --- a/tests/components/ptvsd/test_ptvsd.py +++ b/tests/components/ptvsd/test_ptvsd.py @@ -1,14 +1,12 @@ """Tests for PTVSD Debugger.""" -from unittest.mock import patch - from pytest import mark from homeassistant.bootstrap import _async_set_up_integrations import homeassistant.components.ptvsd as ptvsd_component from homeassistant.setup import async_setup_component -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, patch @mark.skip("causes code cover to fail") diff --git a/tests/components/pushbullet/test_notify.py b/tests/components/pushbullet/test_notify.py index 4c731c1f704..930d9261f9c 100644 --- a/tests/components/pushbullet/test_notify.py +++ b/tests/components/pushbullet/test_notify.py @@ -1,7 +1,6 @@ """The tests for the pushbullet notification platform.""" import json import unittest -from unittest.mock import patch from pushbullet import PushBullet import requests_mock @@ -9,6 +8,7 @@ import requests_mock import homeassistant.components.notify as notify from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant, load_fixture diff --git a/tests/components/pvpc_hourly_pricing/test_config_flow.py b/tests/components/pvpc_hourly_pricing/test_config_flow.py index fbbe87fee5f..d76f74f64a1 100644 --- a/tests/components/pvpc_hourly_pricing/test_config_flow.py +++ b/tests/components/pvpc_hourly_pricing/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for the pvpc_hourly_pricing config_flow.""" from datetime import datetime -from unittest.mock import patch from pytz import timezone @@ -11,6 +10,7 @@ from homeassistant.helpers import entity_registry from .conftest import check_valid_state +from tests.async_mock import patch from tests.common import date_util from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/pvpc_hourly_pricing/test_sensor.py b/tests/components/pvpc_hourly_pricing/test_sensor.py index fdab7fd1008..7ef50113de5 100644 --- a/tests/components/pvpc_hourly_pricing/test_sensor.py +++ b/tests/components/pvpc_hourly_pricing/test_sensor.py @@ -1,7 +1,6 @@ """Tests for the pvpc_hourly_pricing sensor component.""" from datetime import datetime, timedelta import logging -from unittest.mock import patch from pytz import timezone @@ -11,6 +10,7 @@ from homeassistant.core import ATTR_NOW, EVENT_TIME_CHANGED from .conftest import check_valid_state +from tests.async_mock import patch from tests.common import async_setup_component, date_util from tests.test_util.aiohttp import AiohttpClientMocker diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index d122fef0bd2..90d2ac67faf 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -1,11 +1,11 @@ """Test the python_script component.""" import logging -from unittest.mock import mock_open, patch from homeassistant.components.python_script import DOMAIN, FOLDER, execute from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.setup import async_setup_component +from tests.async_mock import mock_open, patch from tests.common import patch_yaml_files diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index afcd2f11802..15518afbc2d 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -1,6 +1,5 @@ """The tests for the Queensland Bushfire Alert Feed platform.""" import datetime -from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -28,6 +27,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed CONFIG = {geo_location.DOMAIN: [{"platform": "qld_bushfire", CONF_RADIUS: 200}]} diff --git a/tests/components/radarr/test_sensor.py b/tests/components/radarr/test_sensor.py index c18476a92a9..0e76e99e721 100644 --- a/tests/components/radarr/test_sensor.py +++ b/tests/components/radarr/test_sensor.py @@ -6,6 +6,7 @@ import pytest import homeassistant.components.radarr.sensor as radarr from homeassistant.const import DATA_GIGABYTES +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -212,7 +213,7 @@ class TestRadarrSetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_no_paths(self, req_mock): """Test getting all disk space.""" config = { @@ -232,7 +233,7 @@ class TestRadarrSetup(unittest.TestCase): assert "Radarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_paths(self, req_mock): """Test getting diskspace for included paths.""" config = { @@ -252,7 +253,7 @@ class TestRadarrSetup(unittest.TestCase): assert "Radarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_commands(self, req_mock): """Test getting running commands.""" config = { @@ -272,7 +273,7 @@ class TestRadarrSetup(unittest.TestCase): assert "Radarr Commands" == device.name assert "pending" == device.device_state_attributes["RescanMovie"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_movies(self, req_mock): """Test getting the number of movies.""" config = { @@ -292,7 +293,7 @@ class TestRadarrSetup(unittest.TestCase): assert "Radarr Movies" == device.name assert "false" == device.device_state_attributes["Assassin's Creed (2016)"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_multiple_days(self, req_mock): """Test the upcoming movies for multiple days.""" config = { @@ -316,7 +317,7 @@ class TestRadarrSetup(unittest.TestCase): ) @pytest.mark.skip - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_today(self, req_mock): """Test filtering for a single day. @@ -342,7 +343,7 @@ class TestRadarrSetup(unittest.TestCase): == device.device_state_attributes["Resident Evil (2017)"] ) - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_system_status(self, req_mock): """Test the getting of the system status.""" config = { @@ -362,7 +363,7 @@ class TestRadarrSetup(unittest.TestCase): assert "4.8.13.1" == device.device_state_attributes["osVersion"] @pytest.mark.skip - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_ssl(self, req_mock): """Test SSL being enabled.""" config = { @@ -387,7 +388,7 @@ class TestRadarrSetup(unittest.TestCase): == device.device_state_attributes["Resident Evil (2017)"] ) - @unittest.mock.patch("requests.get", side_effect=mocked_exception) + @patch("requests.get", side_effect=mocked_exception) def test_exception_handling(self, req_mock): """Test exception being handled.""" config = { diff --git a/tests/components/random/test_binary_sensor.py b/tests/components/random/test_binary_sensor.py index a11b571dd83..975da102ca6 100644 --- a/tests/components/random/test_binary_sensor.py +++ b/tests/components/random/test_binary_sensor.py @@ -1,9 +1,9 @@ """The test for the Random binary sensor platform.""" import unittest -from unittest.mock import patch from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 34e0231d75a..1931a367ee8 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from datetime import datetime, timedelta import unittest -from unittest.mock import patch import pytest @@ -17,6 +16,7 @@ from homeassistant.util import dt as dt_util from .common import wait_recording_done +from tests.async_mock import patch from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/recorder/test_migrate.py b/tests/components/recorder/test_migrate.py index d10dad43d75..d3cf69fc994 100644 --- a/tests/components/recorder/test_migrate.py +++ b/tests/components/recorder/test_migrate.py @@ -1,7 +1,4 @@ """The tests for the Recorder component.""" -# pylint: disable=protected-access -from unittest.mock import call, patch - import pytest from sqlalchemy import create_engine from sqlalchemy.pool import StaticPool @@ -9,6 +6,8 @@ from sqlalchemy.pool import StaticPool from homeassistant.bootstrap import async_setup_component from homeassistant.components.recorder import const, migration, models +# pylint: disable=protected-access +from tests.async_mock import call, patch from tests.components.recorder import models_original diff --git a/tests/components/recorder/test_purge.py b/tests/components/recorder/test_purge.py index e0993b8cffc..4ec08c432b0 100644 --- a/tests/components/recorder/test_purge.py +++ b/tests/components/recorder/test_purge.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta import json import unittest -from unittest.mock import patch from homeassistant.components import recorder from homeassistant.components.recorder.const import DATA_INSTANCE @@ -10,6 +9,7 @@ from homeassistant.components.recorder.models import Events, States from homeassistant.components.recorder.purge import purge_old_data from homeassistant.components.recorder.util import session_scope +from tests.async_mock import patch from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/recorder/test_util.py b/tests/components/recorder/test_util.py index eca146f3efa..8de5acd78db 100644 --- a/tests/components/recorder/test_util.py +++ b/tests/components/recorder/test_util.py @@ -1,11 +1,10 @@ """Test util methods.""" -from unittest.mock import MagicMock, patch - import pytest from homeassistant.components.recorder import util from homeassistant.components.recorder.const import DATA_INSTANCE +from tests.async_mock import MagicMock, patch from tests.common import get_test_home_assistant, init_recorder_component diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index 51de8229347..c2620aa906d 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the Reddit platform.""" import copy import unittest -from unittest.mock import patch from homeassistant.components.reddit.sensor import ( ATTR_BODY, @@ -19,6 +18,7 @@ from homeassistant.components.reddit.sensor import ( from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant VALID_CONFIG = { diff --git a/tests/components/remember_the_milk/test_init.py b/tests/components/remember_the_milk/test_init.py index ba1c24cf6f8..2bba18f0052 100644 --- a/tests/components/remember_the_milk/test_init.py +++ b/tests/components/remember_the_milk/test_init.py @@ -3,10 +3,10 @@ import json import logging import unittest -from unittest.mock import Mock, mock_open, patch import homeassistant.components.remember_the_milk as rtm +from tests.async_mock import Mock, mock_open, patch from tests.common import get_test_home_assistant _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py index a4850793ca7..65ae36c3843 100644 --- a/tests/components/rest/test_binary_sensor.py +++ b/tests/components/rest/test_binary_sensor.py @@ -1,6 +1,5 @@ """The tests for the REST binary sensor platform.""" import unittest -from unittest.mock import Mock, patch import pytest from pytest import raises @@ -15,6 +14,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import template from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index cd2a911292c..c3ed8cea1b9 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -1,6 +1,5 @@ """The tests for the REST sensor platform.""" import unittest -from unittest.mock import Mock, patch import pytest from pytest import raises @@ -16,6 +15,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.config_validation import template from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/rflink/test_binary_sensor.py b/tests/components/rflink/test_binary_sensor.py index 788e6d4981f..6a5a0b7f0e2 100644 --- a/tests/components/rflink/test_binary_sensor.py +++ b/tests/components/rflink/test_binary_sensor.py @@ -5,7 +5,6 @@ Test setup of rflink sensor component/platform. Verify manual and automatic sensor creation. """ from datetime import timedelta -from unittest.mock import patch from homeassistant.components.rflink import CONF_RECONNECT_INTERVAL from homeassistant.const import ( @@ -17,6 +16,7 @@ from homeassistant.const import ( import homeassistant.core as ha import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.rflink.test_init import mock_rflink diff --git a/tests/components/rflink/test_init.py b/tests/components/rflink/test_init.py index 5946adc90a6..fdc60ca7262 100644 --- a/tests/components/rflink/test_init.py +++ b/tests/components/rflink/test_init.py @@ -1,7 +1,5 @@ """Common functions for RFLink component tests and generic platform tests.""" -from unittest.mock import Mock - import pytest from voluptuous.error import MultipleInvalid @@ -17,6 +15,8 @@ from homeassistant.components.rflink import ( ) from homeassistant.const import ATTR_ENTITY_ID, SERVICE_STOP_COVER, SERVICE_TURN_OFF +from tests.async_mock import Mock + async def mock_rflink( hass, config, domain, monkeypatch, failures=None, failcommand=False diff --git a/tests/components/ring/common.py b/tests/components/ring/common.py index 93a6e4f91e0..39b5c339677 100644 --- a/tests/components/ring/common.py +++ b/tests/components/ring/common.py @@ -1,9 +1,8 @@ """Common methods used across the tests for ring devices.""" -from unittest.mock import patch - from homeassistant.components.ring import DOMAIN from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/ring/test_binary_sensor.py b/tests/components/ring/test_binary_sensor.py index 0b73c739503..8edaf2c229b 100644 --- a/tests/components/ring/test_binary_sensor.py +++ b/tests/components/ring/test_binary_sensor.py @@ -1,9 +1,10 @@ """The tests for the Ring binary sensor platform.""" from time import time -from unittest.mock import patch from .common import setup_platform +from tests.async_mock import patch + async def test_binary_sensor(hass, requests_mock): """Test the Ring binary sensors.""" diff --git a/tests/components/script/test_init.py b/tests/components/script/test_init.py index e4a4d4ca239..8dbe43a25ff 100644 --- a/tests/components/script/test_init.py +++ b/tests/components/script/test_init.py @@ -1,7 +1,6 @@ """The tests for the Script component.""" # pylint: disable=protected-access import unittest -from unittest.mock import Mock, patch import pytest @@ -22,6 +21,7 @@ from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.loader import bind_hass from homeassistant.setup import async_setup_component, setup_component +from tests.async_mock import Mock, patch from tests.common import get_test_home_assistant ENTITY_ID = "script.test" diff --git a/tests/components/signal_messenger/test_notify.py b/tests/components/signal_messenger/test_notify.py index dbfd19795e8..a44be249f22 100644 --- a/tests/components/signal_messenger/test_notify.py +++ b/tests/components/signal_messenger/test_notify.py @@ -3,7 +3,6 @@ import os import tempfile import unittest -from unittest.mock import patch from pysignalclirestapi import SignalCliRestApi import requests_mock @@ -11,6 +10,8 @@ import requests_mock import homeassistant.components.signal_messenger.notify as signalmessenger from homeassistant.setup import async_setup_component +from tests.async_mock import patch + BASE_COMPONENT = "notify" diff --git a/tests/components/simplisafe/test_config_flow.py b/tests/components/simplisafe/test_config_flow.py index 5dc0fd5698b..2448b20b084 100644 --- a/tests/components/simplisafe/test_config_flow.py +++ b/tests/components/simplisafe/test_config_flow.py @@ -1,6 +1,5 @@ """Define tests for the SimpliSafe config flow.""" import json -from unittest.mock import MagicMock, PropertyMock, mock_open from simplipy.errors import SimplipyError @@ -9,7 +8,7 @@ from homeassistant.components.simplisafe import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME -from tests.async_mock import patch +from tests.async_mock import MagicMock, PropertyMock, mock_open, patch from tests.common import MockConfigEntry diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index b8c3a2cd2e8..fbafe8aad7d 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -1,12 +1,12 @@ """The tests for SleepIQ binary sensor platform.""" import unittest -from unittest.mock import MagicMock import requests_mock from homeassistant.components.sleepiq import binary_sensor as sleepiq from homeassistant.setup import setup_component +from tests.async_mock import MagicMock from tests.common import get_test_home_assistant from tests.components.sleepiq.test_init import mock_responses diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index 6626be41a6b..9c1c0972fac 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -1,12 +1,12 @@ """The tests for the SleepIQ component.""" import unittest -from unittest.mock import MagicMock, patch import requests_mock from homeassistant import setup import homeassistant.components.sleepiq as sleepiq +from tests.async_mock import MagicMock, patch from tests.common import get_test_home_assistant, load_fixture diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index a049dfd2fbf..d94cd7e4063 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -1,12 +1,12 @@ """The tests for SleepIQ sensor platform.""" import unittest -from unittest.mock import MagicMock import requests_mock import homeassistant.components.sleepiq.sensor as sleepiq from homeassistant.setup import setup_component +from tests.async_mock import MagicMock from tests.common import get_test_home_assistant from tests.components.sleepiq.test_init import mock_responses diff --git a/tests/components/smhi/common.py b/tests/components/smhi/common.py index 6f215840324..92c9e13fb8a 100644 --- a/tests/components/smhi/common.py +++ b/tests/components/smhi/common.py @@ -1,5 +1,5 @@ """Common test utilities.""" -from unittest.mock import Mock +from tests.async_mock import Mock class AsyncMock(Mock): diff --git a/tests/components/smhi/test_init.py b/tests/components/smhi/test_init.py index 450ac7e6ef0..e6b523d96bb 100644 --- a/tests/components/smhi/test_init.py +++ b/tests/components/smhi/test_init.py @@ -1,10 +1,10 @@ """Test SMHI component setup process.""" -from unittest.mock import Mock - from homeassistant.components import smhi from .common import AsyncMock +from tests.async_mock import Mock + TEST_CONFIG = { "config": { "name": "0123456789ABCDEF", diff --git a/tests/components/smtp/test_notify.py b/tests/components/smtp/test_notify.py index 6c7e41a4728..f74d47a21c1 100644 --- a/tests/components/smtp/test_notify.py +++ b/tests/components/smtp/test_notify.py @@ -1,10 +1,10 @@ """The tests for the notify smtp platform.""" import re import unittest -from unittest.mock import patch from homeassistant.components.smtp.notify import MailNotificationService +from tests.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/solaredge/test_config_flow.py b/tests/components/solaredge/test_config_flow.py index 759639362e4..61bc5f9ac6c 100644 --- a/tests/components/solaredge/test_config_flow.py +++ b/tests/components/solaredge/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the SolarEdge config flow.""" -from unittest.mock import Mock, patch - import pytest from requests.exceptions import ConnectTimeout, HTTPError @@ -9,6 +7,7 @@ from homeassistant.components.solaredge import config_flow from homeassistant.components.solaredge.const import CONF_SITE_ID, DEFAULT_NAME from homeassistant.const import CONF_API_KEY, CONF_NAME +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry NAME = "solaredge site 1 2 3" diff --git a/tests/components/soma/test_config_flow.py b/tests/components/soma/test_config_flow.py index 1d00f83a608..929463ecf81 100644 --- a/tests/components/soma/test_config_flow.py +++ b/tests/components/soma/test_config_flow.py @@ -1,12 +1,11 @@ """Tests for the Soma config flow.""" -from unittest.mock import patch - from api.soma_api import SomaApi from requests import RequestException from homeassistant import data_entry_flow from homeassistant.components.soma import DOMAIN, config_flow +from tests.async_mock import patch from tests.common import MockConfigEntry MOCK_HOST = "123.45.67.89" diff --git a/tests/components/somfy/test_config_flow.py b/tests/components/somfy/test_config_flow.py index f195b640240..1823cb3c3ab 100644 --- a/tests/components/somfy/test_config_flow.py +++ b/tests/components/somfy/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for the Somfy config flow.""" import asyncio -from unittest.mock import patch import pytest @@ -8,6 +7,7 @@ from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.somfy import DOMAIN, config_flow from homeassistant.helpers import config_entry_oauth2_flow +from tests.async_mock import patch from tests.common import MockConfigEntry CLIENT_SECRET_VALUE = "5678" diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 1629a3d29c2..96585f87068 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -8,6 +8,7 @@ import pytest import homeassistant.components.sonarr.sensor as sonarr from homeassistant.const import DATA_GIGABYTES, UNIT_PERCENTAGE +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -491,7 +492,7 @@ class TestSonarrSetup(unittest.TestCase): """Stop everything that was started.""" self.hass.stop() - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_no_paths(self, req_mock): """Test getting all disk space.""" config = { @@ -511,7 +512,7 @@ class TestSonarrSetup(unittest.TestCase): assert "Sonarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_diskspace_paths(self, req_mock): """Test getting diskspace for included paths.""" config = { @@ -531,7 +532,7 @@ class TestSonarrSetup(unittest.TestCase): assert "Sonarr Disk Space" == device.name assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_commands(self, req_mock): """Test getting running commands.""" config = { @@ -551,7 +552,7 @@ class TestSonarrSetup(unittest.TestCase): assert "Sonarr Commands" == device.name assert "pending" == device.device_state_attributes["RescanSeries"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_queue(self, req_mock): """Test getting downloads in the queue.""" config = { @@ -574,7 +575,7 @@ class TestSonarrSetup(unittest.TestCase): == device.device_state_attributes["Game of Thrones S03E08"] ) - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_series(self, req_mock): """Test getting the number of series.""" config = { @@ -596,7 +597,7 @@ class TestSonarrSetup(unittest.TestCase): "26/26 Episodes" == device.device_state_attributes["Marvel's Daredevil"] ) - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_wanted(self, req_mock): """Test getting wanted episodes.""" config = { @@ -618,7 +619,7 @@ class TestSonarrSetup(unittest.TestCase): "2014-02-03" == device.device_state_attributes["Archer (2009) S05E04"] ) - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_multiple_days(self, req_mock): """Test the upcoming episodes for multiple days.""" config = { @@ -639,7 +640,7 @@ class TestSonarrSetup(unittest.TestCase): assert "S04E11" == device.device_state_attributes["Bob's Burgers"] @pytest.mark.skip - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_upcoming_today(self, req_mock): """Test filtering for a single day. @@ -662,7 +663,7 @@ class TestSonarrSetup(unittest.TestCase): assert "Sonarr Upcoming" == device.name assert "S04E11" == device.device_state_attributes["Bob's Burgers"] - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_system_status(self, req_mock): """Test getting system status.""" config = { @@ -682,7 +683,7 @@ class TestSonarrSetup(unittest.TestCase): assert "6.2.9200.0" == device.device_state_attributes["osVersion"] @pytest.mark.skip - @unittest.mock.patch("requests.get", side_effect=mocked_requests_get) + @patch("requests.get", side_effect=mocked_requests_get) def test_ssl(self, req_mock): """Test SSL being enabled.""" config = { @@ -704,7 +705,7 @@ class TestSonarrSetup(unittest.TestCase): assert "Sonarr Upcoming" == device.name assert "S04E11" == device.device_state_attributes["Bob's Burgers"] - @unittest.mock.patch("requests.get", side_effect=mocked_exception) + @patch("requests.get", side_effect=mocked_exception) def test_exception_handling(self, req_mock): """Test exception being handled.""" config = { diff --git a/tests/components/soundtouch/test_media_player.py b/tests/components/soundtouch/test_media_player.py index bc3cc1bc977..0d796cd8785 100644 --- a/tests/components/soundtouch/test_media_player.py +++ b/tests/components/soundtouch/test_media_player.py @@ -1,6 +1,4 @@ """Test the Soundtouch component.""" -from unittest.mock import call - from libsoundtouch.device import ( Config, Preset, @@ -27,7 +25,7 @@ from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.helpers.discovery import async_load_platform from homeassistant.setup import async_setup_component -from tests.async_mock import patch +from tests.async_mock import call, patch # pylint: disable=super-init-not-called diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index 3644ca462ca..7115151451f 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -1,6 +1,4 @@ """Tests for the Spotify config flow.""" -from unittest.mock import patch - from spotipy import SpotifyException from homeassistant import data_entry_flow, setup @@ -12,6 +10,7 @@ from homeassistant.components.spotify.const import ( from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.helpers import config_entry_oauth2_flow +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index 61a0abb6265..721cf71303d 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta import statistics import unittest -from unittest.mock import patch import pytest @@ -12,6 +11,7 @@ from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CE from homeassistant.setup import setup_component from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.common import ( fire_time_changed, get_test_home_assistant, diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py index 12fa8e8a4d6..dc7892e069a 100644 --- a/tests/components/stream/test_init.py +++ b/tests/components/stream/test_init.py @@ -1,6 +1,4 @@ """The tests for stream.""" -from unittest.mock import MagicMock, patch - import pytest from homeassistant.components.stream.const import ( @@ -14,7 +12,7 @@ from homeassistant.const import CONF_FILENAME from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, MagicMock, patch async def test_record_service_invalid_file(hass): diff --git a/tests/components/stream/test_recorder.py b/tests/components/stream/test_recorder.py index 95eeeecf7ad..fbbeaf0ff44 100644 --- a/tests/components/stream/test_recorder.py +++ b/tests/components/stream/test_recorder.py @@ -1,7 +1,6 @@ """The tests for hls streams.""" from datetime import timedelta from io import BytesIO -from unittest.mock import patch import pytest @@ -10,6 +9,7 @@ from homeassistant.components.stream.recorder import recorder_save_worker from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed from tests.components.stream.common import generate_h264_video, preload_stream diff --git a/tests/components/sun/test_init.py b/tests/components/sun/test_init.py index e04de7e2578..e023814725b 100644 --- a/tests/components/sun/test_init.py +++ b/tests/components/sun/test_init.py @@ -1,6 +1,5 @@ """The tests for the Sun component.""" from datetime import datetime, timedelta -from unittest.mock import patch from pytest import mark @@ -10,6 +9,8 @@ import homeassistant.core as ha from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch + async def test_setting_rising(hass): """Test retrieving sun setting and rising.""" diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index fe32fca9cb7..e4e8564f5bb 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -1,6 +1,5 @@ """The test for switch device automation.""" from datetime import timedelta -from unittest.mock import patch import pytest @@ -11,6 +10,7 @@ from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import ( MockConfigEntry, async_get_device_automation_capabilities, diff --git a/tests/components/synology_dsm/conftest.py b/tests/components/synology_dsm/conftest.py index 7829a3cc999..67c3cab659e 100644 --- a/tests/components/synology_dsm/conftest.py +++ b/tests/components/synology_dsm/conftest.py @@ -1,8 +1,8 @@ """Configure Synology DSM tests.""" -from unittest.mock import patch - import pytest +from tests.async_mock import patch + @pytest.fixture(name="dsm_bypass_setup", autouse=True) def dsm_bypass_setup_fixture(): diff --git a/tests/components/synology_dsm/test_config_flow.py b/tests/components/synology_dsm/test_config_flow.py index 795348d900c..66fc943920e 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for the Synology DSM config flow.""" import logging -from unittest.mock import MagicMock, Mock, patch import pytest from synology_dsm.exceptions import ( @@ -33,6 +32,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import MagicMock, Mock, patch from tests.common import MockConfigEntry _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/system_log/test_init.py b/tests/components/system_log/test_init.py index 6408b1625f5..009701ca886 100644 --- a/tests/components/system_log/test_init.py +++ b/tests/components/system_log/test_init.py @@ -1,11 +1,12 @@ """Test system log component.""" import logging -from unittest.mock import MagicMock, patch from homeassistant.bootstrap import async_setup_component from homeassistant.components import system_log from homeassistant.core import callback +from tests.async_mock import MagicMock, patch + _LOGGER = logging.getLogger("test_logger") BASIC_CONFIG = {"system_log": {"max_entries": 2}} diff --git a/tests/components/tcp/test_binary_sensor.py b/tests/components/tcp/test_binary_sensor.py index 2dc16ad79c7..4cde4d9ac31 100644 --- a/tests/components/tcp/test_binary_sensor.py +++ b/tests/components/tcp/test_binary_sensor.py @@ -1,11 +1,11 @@ """The tests for the TCP binary sensor platform.""" import unittest -from unittest.mock import Mock, patch from homeassistant.components.tcp import binary_sensor as bin_tcp import homeassistant.components.tcp.sensor as tcp from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant import tests.components.tcp.test_sensor as test_tcp diff --git a/tests/components/tcp/test_sensor.py b/tests/components/tcp/test_sensor.py index 8e79d4e514d..b06652dc53f 100644 --- a/tests/components/tcp/test_sensor.py +++ b/tests/components/tcp/test_sensor.py @@ -2,7 +2,6 @@ from copy import copy import socket import unittest -from unittest.mock import Mock, patch from uuid import uuid4 import homeassistant.components.tcp.sensor as tcp @@ -10,6 +9,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.template import Template from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant TEST_CONFIG = { diff --git a/tests/components/time_date/test_sensor.py b/tests/components/time_date/test_sensor.py index 2aae99f93a5..80a081cd524 100644 --- a/tests/components/time_date/test_sensor.py +++ b/tests/components/time_date/test_sensor.py @@ -1,10 +1,10 @@ """The tests for time_date sensor platform.""" import unittest -from unittest.mock import patch import homeassistant.components.time_date.sensor as time_date import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index dea116b3905..75bafba634f 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from datetime import timedelta import logging -from unittest.mock import patch import pytest @@ -42,6 +41,7 @@ from homeassistant.helpers import config_validation as cv, entity_registry from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow +from tests.async_mock import patch from tests.common import async_fire_time_changed _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 1da0c16d43c..4febd1aa8d1 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -1,7 +1,6 @@ """Test Times of the Day Binary Sensor.""" from datetime import datetime, timedelta import unittest -from unittest.mock import patch import pytz @@ -12,6 +11,7 @@ from homeassistant.helpers.sun import get_astral_event_date, get_astral_event_ne from homeassistant.setup import setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/toon/test_config_flow.py b/tests/components/toon/test_config_flow.py index 45d16908446..fdf97243a3a 100644 --- a/tests/components/toon/test_config_flow.py +++ b/tests/components/toon/test_config_flow.py @@ -1,7 +1,5 @@ """Tests for the Toon config flow.""" -from unittest.mock import patch - import pytest from toonapilib.toonapilibexceptions import ( AgreementsRetrievalError, @@ -22,6 +20,7 @@ from homeassistant.components.toon.const import ( from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component +from tests.async_mock import patch from tests.common import MockConfigEntry FIXTURE_APP = { diff --git a/tests/components/totalconnect/test_config_flow.py b/tests/components/totalconnect/test_config_flow.py index b77198fa9b2..80cfe7a81f8 100644 --- a/tests/components/totalconnect/test_config_flow.py +++ b/tests/components/totalconnect/test_config_flow.py @@ -1,11 +1,10 @@ """Tests for the iCloud config flow.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components.totalconnect.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry USERNAME = "username@me.com" diff --git a/tests/components/tplink/test_common.py b/tests/components/tplink/test_common.py index ef4f1d22a2d..a2bd7ef87ff 100644 --- a/tests/components/tplink/test_common.py +++ b/tests/components/tplink/test_common.py @@ -1,12 +1,13 @@ """Common code tests.""" from datetime import timedelta -from unittest.mock import MagicMock from pyHS100 import SmartDeviceException from homeassistant.components.tplink.common import async_add_entities_retry from homeassistant.helpers.typing import HomeAssistantType +from tests.async_mock import MagicMock + async def test_async_add_entities_retry(hass: HomeAssistantType): """Test interval callback.""" diff --git a/tests/components/tplink/test_light.py b/tests/components/tplink/test_light.py index 0986362ba28..241789270d7 100644 --- a/tests/components/tplink/test_light.py +++ b/tests/components/tplink/test_light.py @@ -1,6 +1,5 @@ """Tests for light platform.""" from typing import Callable, NamedTuple -from unittest.mock import Mock, PropertyMock, patch from pyHS100 import SmartDeviceException import pytest @@ -30,6 +29,8 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, PropertyMock, patch + class LightMockData(NamedTuple): """Mock light data.""" diff --git a/tests/components/traccar/test_init.py b/tests/components/traccar/test_init.py index a13a3d25a6c..2804d90ac33 100644 --- a/tests/components/traccar/test_init.py +++ b/tests/components/traccar/test_init.py @@ -1,6 +1,4 @@ """The tests the for Traccar device tracker platform.""" -from unittest.mock import Mock, patch - import pytest from homeassistant import data_entry_flow @@ -16,6 +14,8 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import DATA_DISPATCHER from homeassistant.setup import async_setup_component +from tests.async_mock import Mock, patch + HOME_LATITUDE = 37.239622 HOME_LONGITUDE = -115.815811 diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index e4bdd140faa..8ffc25aba5a 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -1,7 +1,6 @@ """Tradfri lights platform tests.""" from copy import deepcopy -from unittest.mock import MagicMock, Mock, PropertyMock, patch import pytest from pytradfri.device import Device @@ -10,6 +9,7 @@ from pytradfri.device.light_control import LightControl from homeassistant.components import tradfri +from tests.async_mock import MagicMock, Mock, PropertyMock, patch from tests.common import MockConfigEntry DEFAULT_TEST_FEATURES = { diff --git a/tests/components/transmission/test_config_flow.py b/tests/components/transmission/test_config_flow.py index bb790b025ea..4436a6adf21 100644 --- a/tests/components/transmission/test_config_flow.py +++ b/tests/components/transmission/test_config_flow.py @@ -1,6 +1,5 @@ """Tests for Transmission config flow.""" from datetime import timedelta -from unittest.mock import patch import pytest from transmissionrpc.error import TransmissionError @@ -22,6 +21,7 @@ from homeassistant.const import ( CONF_USERNAME, ) +from tests.async_mock import patch from tests.common import MockConfigEntry NAME = "Transmission" diff --git a/tests/components/transport_nsw/test_sensor.py b/tests/components/transport_nsw/test_sensor.py index 75881e113d7..ab66260a55e 100644 --- a/tests/components/transport_nsw/test_sensor.py +++ b/tests/components/transport_nsw/test_sensor.py @@ -1,9 +1,9 @@ """The tests for the Transport NSW (AU) sensor platform.""" import unittest -from unittest.mock import patch from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant VALID_CONFIG = { diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index d78cf793d2f..fc93df0aacf 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -1,10 +1,10 @@ """The test for the Trend sensor platform.""" from datetime import timedelta -from unittest.mock import patch from homeassistant import setup import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 61bb78a0827..3fbf1245fa3 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -1,6 +1,5 @@ """The tests for the TTS component.""" import ctypes -from unittest.mock import PropertyMock, patch import pytest import yarl @@ -18,6 +17,7 @@ from homeassistant.components.tts import _get_cache_files from homeassistant.const import HTTP_NOT_FOUND from homeassistant.setup import async_setup_component +from tests.async_mock import PropertyMock, patch from tests.common import assert_setup_component, async_mock_service diff --git a/tests/components/twilio/test_init.py b/tests/components/twilio/test_init.py index ee7f072a65c..185139077df 100644 --- a/tests/components/twilio/test_init.py +++ b/tests/components/twilio/test_init.py @@ -1,10 +1,10 @@ """Test the init file of Twilio.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components import twilio from homeassistant.core import callback +from tests.async_mock import patch + async def test_config_flow_registers_webhook(hass, aiohttp_client): """Test setting up Twilio and sending webhook.""" diff --git a/tests/components/twitch/test_twitch.py b/tests/components/twitch/test_twitch.py index 6c656f874d0..3e777fa3d03 100644 --- a/tests/components/twitch/test_twitch.py +++ b/tests/components/twitch/test_twitch.py @@ -1,12 +1,12 @@ """The tests for an update of the Twitch component.""" -from unittest.mock import MagicMock, patch - from requests import HTTPError from twitch.resources import Channel, Follow, Stream, Subscription, User from homeassistant.components import sensor from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock, patch + ENTITY_ID = "sensor.channel123" CONFIG = { sensor.DOMAIN: { diff --git a/tests/components/uptime/test_sensor.py b/tests/components/uptime/test_sensor.py index 0a9d227681b..111114d8aca 100644 --- a/tests/components/uptime/test_sensor.py +++ b/tests/components/uptime/test_sensor.py @@ -2,11 +2,11 @@ import asyncio from datetime import timedelta import unittest -from unittest.mock import patch from homeassistant.components.uptime.sensor import UptimeSensor from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant diff --git a/tests/components/usgs_earthquakes_feed/test_geo_location.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py index 4823d2eb2da..9bd718d1933 100644 --- a/tests/components/usgs_earthquakes_feed/test_geo_location.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -1,6 +1,5 @@ """The tests for the USGS Earthquake Hazards Program Feed platform.""" import datetime -from unittest.mock import MagicMock, call, patch from homeassistant.components import geo_location from homeassistant.components.geo_location import ATTR_SOURCE @@ -32,6 +31,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, call, patch from tests.common import assert_setup_component, async_fire_time_changed CONFIG = { diff --git a/tests/components/utility_meter/test_init.py b/tests/components/utility_meter/test_init.py index f6c1e6c8ead..7116077177a 100644 --- a/tests/components/utility_meter/test_init.py +++ b/tests/components/utility_meter/test_init.py @@ -1,7 +1,6 @@ """The tests for the utility_meter component.""" from datetime import timedelta import logging -from unittest.mock import patch from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.utility_meter.const import ( @@ -19,6 +18,8 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch + _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/utility_meter/test_sensor.py b/tests/components/utility_meter/test_sensor.py index 6118d74d0dd..d7c888802ed 100644 --- a/tests/components/utility_meter/test_sensor.py +++ b/tests/components/utility_meter/test_sensor.py @@ -2,7 +2,6 @@ from contextlib import contextmanager from datetime import timedelta import logging -from unittest.mock import patch from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.utility_meter.const import ( @@ -20,6 +19,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index daeffb4ed1d..3bccacc0a94 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -1,12 +1,11 @@ """Tests for the Velbus config flow.""" -from unittest.mock import Mock, patch - import pytest from homeassistant import data_entry_flow from homeassistant.components.velbus import config_flow from homeassistant.const import CONF_NAME, CONF_PORT +from tests.async_mock import Mock, patch from tests.common import MockConfigEntry PORT_SERIAL = "/dev/ttyACME100" diff --git a/tests/components/vera/test_binary_sensor.py b/tests/components/vera/test_binary_sensor.py index 72651d6eda4..4b0d41d9a1e 100644 --- a/tests/components/vera/test_binary_sensor.py +++ b/tests/components/vera/test_binary_sensor.py @@ -1,12 +1,12 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_binary_sensor( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_climate.py b/tests/components/vera/test_climate.py index 9e5fa983ed0..f52bf375d8e 100644 --- a/tests/components/vera/test_climate.py +++ b/tests/components/vera/test_climate.py @@ -1,6 +1,4 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.components.climate.const import ( @@ -15,6 +13,8 @@ from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_climate( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index 5f4536decec..3915d4d0577 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -1,6 +1,4 @@ """Vera tests.""" -from unittest.mock import MagicMock - from mock import patch from requests.exceptions import RequestException @@ -14,6 +12,7 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) +from tests.async_mock import MagicMock from tests.common import MockConfigEntry diff --git a/tests/components/vera/test_cover.py b/tests/components/vera/test_cover.py index 62cd47f831c..a2dae2bd7f8 100644 --- a/tests/components/vera/test_cover.py +++ b/tests/components/vera/test_cover.py @@ -1,12 +1,12 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_cover( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_light.py b/tests/components/vera/test_light.py index fefa07ffa6e..14194d0af52 100644 --- a/tests/components/vera/test_light.py +++ b/tests/components/vera/test_light.py @@ -1,6 +1,4 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_HS_COLOR @@ -8,6 +6,8 @@ from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_light( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_lock.py b/tests/components/vera/test_lock.py index d1b2209294a..901e09040e9 100644 --- a/tests/components/vera/test_lock.py +++ b/tests/components/vera/test_lock.py @@ -1,6 +1,4 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED @@ -8,6 +6,8 @@ from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_lock( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_scene.py b/tests/components/vera/test_scene.py index 732a331681b..8f96b7a133a 100644 --- a/tests/components/vera/test_scene.py +++ b/tests/components/vera/test_scene.py @@ -1,12 +1,12 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_scene( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/vera/test_sensor.py b/tests/components/vera/test_sensor.py index c915c5ead0f..cb50ad82789 100644 --- a/tests/components/vera/test_sensor.py +++ b/tests/components/vera/test_sensor.py @@ -1,6 +1,5 @@ """Vera tests.""" from typing import Any, Callable, Tuple -from unittest.mock import MagicMock import pyvera as pv @@ -9,6 +8,8 @@ from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def run_sensor_test( hass: HomeAssistant, diff --git a/tests/components/vera/test_switch.py b/tests/components/vera/test_switch.py index c41afad4759..2a8bfe68185 100644 --- a/tests/components/vera/test_switch.py +++ b/tests/components/vera/test_switch.py @@ -1,12 +1,12 @@ """Vera tests.""" -from unittest.mock import MagicMock - import pyvera as pv from homeassistant.core import HomeAssistant from .common import ComponentFactory, new_simple_controller_config +from tests.async_mock import MagicMock + async def test_switch( hass: HomeAssistant, vera_component_factory: ComponentFactory diff --git a/tests/components/verisure/test_ethernet_status.py b/tests/components/verisure/test_ethernet_status.py index 611adde19d9..139ac01a1c6 100644 --- a/tests/components/verisure/test_ethernet_status.py +++ b/tests/components/verisure/test_ethernet_status.py @@ -1,11 +1,12 @@ """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 +from tests.async_mock import patch + CONFIG = { "verisure": { "username": "test", diff --git a/tests/components/verisure/test_lock.py b/tests/components/verisure/test_lock.py index d41bbab2037..decce67dc11 100644 --- a/tests/components/verisure/test_lock.py +++ b/tests/components/verisure/test_lock.py @@ -1,7 +1,6 @@ """Tests for the Verisure platform.""" from contextlib import contextmanager -from unittest.mock import call, patch from homeassistant.components.lock import ( DOMAIN as LOCK_DOMAIN, @@ -12,6 +11,8 @@ from homeassistant.components.verisure import DOMAIN as VERISURE_DOMAIN from homeassistant.const import STATE_UNLOCKED from homeassistant.setup import async_setup_component +from tests.async_mock import call, patch + NO_DEFAULT_LOCK_CODE_CONFIG = { "verisure": { "username": "test", diff --git a/tests/components/version/test_sensor.py b/tests/components/version/test_sensor.py index 164b4090e5f..471043ae3ae 100644 --- a/tests/components/version/test_sensor.py +++ b/tests/components/version/test_sensor.py @@ -1,8 +1,8 @@ """The test for the version sensor platform.""" -from unittest.mock import patch - from homeassistant.setup import async_setup_component +from tests.async_mock import patch + MOCK_VERSION = "10.0" diff --git a/tests/components/vesync/test_config_flow.py b/tests/components/vesync/test_config_flow.py index 39b847effc5..aedf94da4ab 100644 --- a/tests/components/vesync/test_config_flow.py +++ b/tests/components/vesync/test_config_flow.py @@ -1,10 +1,9 @@ """Test for vesync config flow.""" -from unittest.mock import patch - from homeassistant import data_entry_flow from homeassistant.components.vesync import DOMAIN, config_flow from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from tests.async_mock import patch from tests.common import MockConfigEntry diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index b4a2148b8da..7a2ff1d1c7a 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -3,7 +3,6 @@ from contextlib import asynccontextmanager from datetime import timedelta import logging from typing import Any, Dict, List, Optional -from unittest.mock import call import pytest from pytest import raises @@ -73,7 +72,7 @@ from .const import ( VOLUME_STEP, ) -from tests.async_mock import patch +from tests.async_mock import call, patch from tests.common import MockConfigEntry, async_fire_time_changed _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/vultr/test_binary_sensor.py b/tests/components/vultr/test_binary_sensor.py index f57926f30c8..609cdbf6a9e 100644 --- a/tests/components/vultr/test_binary_sensor.py +++ b/tests/components/vultr/test_binary_sensor.py @@ -1,7 +1,6 @@ """Test the Vultr binary sensor platform.""" import json import unittest -from unittest.mock import patch import pytest import requests_mock @@ -20,6 +19,7 @@ from homeassistant.components.vultr import ( ) from homeassistant.const import CONF_NAME, CONF_PLATFORM +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture from tests.components.vultr.test_init import VALID_CONFIG diff --git a/tests/components/vultr/test_init.py b/tests/components/vultr/test_init.py index e371e785c92..6035ac547af 100644 --- a/tests/components/vultr/test_init.py +++ b/tests/components/vultr/test_init.py @@ -2,13 +2,13 @@ from copy import deepcopy import json import unittest -from unittest.mock import patch import requests_mock from homeassistant import setup import homeassistant.components.vultr as vultr +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture VALID_CONFIG = {"vultr": {"api_key": "ABCDEFG1234567"}} diff --git a/tests/components/vultr/test_sensor.py b/tests/components/vultr/test_sensor.py index 80fd05a41cc..1ced0fec82f 100644 --- a/tests/components/vultr/test_sensor.py +++ b/tests/components/vultr/test_sensor.py @@ -1,7 +1,6 @@ """The tests for the Vultr sensor platform.""" import json import unittest -from unittest.mock import patch import pytest import requests_mock @@ -17,6 +16,7 @@ from homeassistant.const import ( DATA_GIGABYTES, ) +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture from tests.components.vultr.test_init import VALID_CONFIG diff --git a/tests/components/vultr/test_switch.py b/tests/components/vultr/test_switch.py index 6a5c382a2d2..594617bdfd9 100644 --- a/tests/components/vultr/test_switch.py +++ b/tests/components/vultr/test_switch.py @@ -1,7 +1,6 @@ """Test the Vultr switch platform.""" import json import unittest -from unittest.mock import patch import pytest import requests_mock @@ -20,6 +19,7 @@ from homeassistant.components.vultr import ( ) from homeassistant.const import CONF_NAME, CONF_PLATFORM +from tests.async_mock import patch from tests.common import get_test_home_assistant, load_fixture from tests.components.vultr.test_init import VALID_CONFIG diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index e0f12f9c7f8..ed4045bcb44 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -1,11 +1,11 @@ """The tests for the wake on lan switch platform.""" import unittest -from unittest.mock import patch import homeassistant.components.switch as switch from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import get_test_home_assistant, mock_service from tests.components.switch import common diff --git a/tests/components/webhook/test_init.py b/tests/components/webhook/test_init.py index 733ed32da78..7d4ce563b03 100644 --- a/tests/components/webhook/test_init.py +++ b/tests/components/webhook/test_init.py @@ -1,10 +1,10 @@ """Test the webhook component.""" -from unittest.mock import Mock - import pytest from homeassistant.setup import async_setup_component +from tests.async_mock import Mock + @pytest.fixture def mock_client(hass, hass_client): diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index cc889eca064..e4395769562 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -25,7 +25,7 @@ from homeassistant.const import ( from homeassistant.setup import async_setup_component if sys.version_info >= (3, 8, 0): - from unittest.mock import patch + from tests.async_mock import patch else: from tests.async_mock import patch diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py index 041c0e76533..2d656de8eeb 100644 --- a/tests/components/websocket_api/test_init.py +++ b/tests/components/websocket_api/test_init.py @@ -1,11 +1,11 @@ """Tests for the Home Assistant Websocket API.""" -from unittest.mock import Mock, patch - from aiohttp import WSMsgType import voluptuous as vol from homeassistant.components.websocket_api import const, messages +from tests.async_mock import Mock, patch + async def test_invalid_message_format(websocket_client): """Test sending invalid JSON.""" diff --git a/tests/components/withings/test_common.py b/tests/components/withings/test_common.py index f0f3a160332..f0528c36005 100644 --- a/tests/components/withings/test_common.py +++ b/tests/components/withings/test_common.py @@ -1,6 +1,5 @@ """Tests for the Withings component.""" from datetime import timedelta -from unittest.mock import patch import pytest from withings_api import WithingsApi @@ -13,7 +12,7 @@ from homeassistant.components.withings.common import ( from homeassistant.exceptions import PlatformNotReady from homeassistant.util import dt -from tests.async_mock import MagicMock +from tests.async_mock import MagicMock, patch @pytest.fixture(name="withings_api") diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index 29d5e4f03ef..c476c9fd0e0 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -1,6 +1,5 @@ """Tests the Home Assistant workday binary sensor.""" from datetime import date -from unittest.mock import patch import pytest import voluptuous as vol @@ -8,6 +7,7 @@ import voluptuous as vol import homeassistant.components.workday.binary_sensor as binary_sensor from homeassistant.setup import setup_component +from tests.async_mock import patch from tests.common import assert_setup_component, get_test_home_assistant FUNCTION_PATH = "homeassistant.components.workday.binary_sensor.get_date" diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 023deb93c81..619293be676 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -1,13 +1,11 @@ """Test the Xiaomi Miio config flow.""" -from unittest.mock import Mock - from miio import DeviceException from homeassistant import config_entries from homeassistant.components.xiaomi_miio import config_flow, const from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN -from tests.async_mock import patch +from tests.async_mock import Mock, patch TEST_HOST = "1.2.3.4" TEST_TOKEN = "12345678901234567890123456789012" diff --git a/tests/components/yamaha/test_media_player.py b/tests/components/yamaha/test_media_player.py index 6b101167c85..2f47ddc7355 100644 --- a/tests/components/yamaha/test_media_player.py +++ b/tests/components/yamaha/test_media_player.py @@ -1,11 +1,11 @@ """The tests for the Yamaha Media player platform.""" import unittest -from unittest.mock import MagicMock, patch import homeassistant.components.media_player as mp from homeassistant.components.yamaha import media_player as yamaha from homeassistant.setup import setup_component +from tests.async_mock import MagicMock, patch from tests.common import get_test_home_assistant diff --git a/tests/components/yessssms/test_notify.py b/tests/components/yessssms/test_notify.py index 992185bf102..d940e782be4 100644 --- a/tests/components/yessssms/test_notify.py +++ b/tests/components/yessssms/test_notify.py @@ -1,7 +1,6 @@ """The tests for the notify yessssms platform.""" import logging import unittest -from unittest.mock import patch import pytest import requests_mock @@ -16,6 +15,8 @@ from homeassistant.const import ( ) from homeassistant.setup import async_setup_component +from tests.async_mock import patch + @pytest.fixture(name="config") def config_data(): diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index d676e88bfc3..d8dcbe367de 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -1,11 +1,11 @@ """The tests for the Yr sensor platform.""" from datetime import datetime -from unittest.mock import patch from homeassistant.bootstrap import async_setup_component from homeassistant.const import DEGREE, SPEED_METERS_PER_SECOND, UNIT_PERCENTAGE import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import assert_setup_component, load_fixture NOW = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC) diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index 9b139317825..4efc6538d7c 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -1,6 +1,5 @@ """Common test objects.""" import time -from unittest.mock import Mock from zigpy.device import Device as zigpy_dev from zigpy.endpoint import Endpoint as zigpy_ep @@ -14,7 +13,7 @@ import zigpy.zdo.types import homeassistant.components.zha.core.const as zha_const from homeassistant.util import slugify -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, Mock class FakeEndpoint: diff --git a/tests/components/zha/test_fan.py b/tests/components/zha/test_fan.py index 399982df37a..b110656c1dd 100644 --- a/tests/components/zha/test_fan.py +++ b/tests/components/zha/test_fan.py @@ -1,6 +1,4 @@ """Test zha fan.""" -from unittest.mock import call - import pytest import zigpy.profiles.zha as zha import zigpy.zcl.clusters.general as general @@ -35,6 +33,8 @@ from .common import ( send_attributes_report, ) +from tests.async_mock import call + IEEE_GROUPABLE_DEVICE = "01:2d:6f:00:0a:90:69:e8" IEEE_GROUPABLE_DEVICE2 = "02:2d:6f:00:0a:90:69:e8" diff --git a/tests/components/zha/test_light.py b/tests/components/zha/test_light.py index 915ace7c7f2..a1a081d7e8b 100644 --- a/tests/components/zha/test_light.py +++ b/tests/components/zha/test_light.py @@ -1,6 +1,5 @@ """Test zha light.""" from datetime import timedelta -from unittest.mock import MagicMock, call, sentinel import pytest import zigpy.profiles.zha as zha @@ -23,7 +22,7 @@ from .common import ( send_attributes_report, ) -from tests.async_mock import AsyncMock, patch +from tests.async_mock import AsyncMock, MagicMock, call, patch, sentinel from tests.common import async_fire_time_changed ON = 1 diff --git a/tests/components/zwave/conftest.py b/tests/components/zwave/conftest.py index f80c55f7767..50edcfec157 100644 --- a/tests/components/zwave/conftest.py +++ b/tests/components/zwave/conftest.py @@ -1,8 +1,7 @@ """Fixtures for Z-Wave tests.""" -from unittest.mock import MagicMock, patch - import pytest +from tests.async_mock import MagicMock, patch from tests.mock.zwave import MockNetwork, MockOption diff --git a/tests/components/zwave/test_binary_sensor.py b/tests/components/zwave/test_binary_sensor.py index 54270cdc3f4..8ac6370ad35 100644 --- a/tests/components/zwave/test_binary_sensor.py +++ b/tests/components/zwave/test_binary_sensor.py @@ -1,9 +1,9 @@ """Test Z-Wave binary sensors.""" import datetime -from unittest.mock import patch from homeassistant.components.zwave import binary_sensor, const +from tests.async_mock import patch from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed diff --git a/tests/components/zwave/test_cover.py b/tests/components/zwave/test_cover.py index e8b784feefe..cde0957e2b3 100644 --- a/tests/components/zwave/test_cover.py +++ b/tests/components/zwave/test_cover.py @@ -1,6 +1,4 @@ """Test Z-Wave cover devices.""" -from unittest.mock import MagicMock - from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN from homeassistant.components.zwave import ( CONF_INVERT_OPENCLOSE_BUTTONS, @@ -9,6 +7,7 @@ from homeassistant.components.zwave import ( cover, ) +from tests.async_mock import MagicMock from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index b71758469d0..19733b045dc 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -3,7 +3,6 @@ import asyncio from collections import OrderedDict from datetime import datetime import unittest -from unittest.mock import MagicMock, patch import pytest from pytz import utc @@ -23,7 +22,7 @@ from homeassistant.helpers.device_registry import async_get_registry as get_dev_ from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.setup import setup_component -from tests.async_mock import AsyncMock +from tests.async_mock import AsyncMock, MagicMock, patch from tests.common import async_fire_time_changed, get_test_home_assistant, mock_registry from tests.mock.zwave import MockEntityValues, MockNetwork, MockNode, MockValue diff --git a/tests/components/zwave/test_light.py b/tests/components/zwave/test_light.py index fc62ef880f6..1b973294daf 100644 --- a/tests/components/zwave/test_light.py +++ b/tests/components/zwave/test_light.py @@ -1,6 +1,4 @@ """Test Z-Wave lights.""" -from unittest.mock import MagicMock, patch - from homeassistant.components import zwave from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -16,6 +14,7 @@ from homeassistant.components.light import ( ) from homeassistant.components.zwave import const, light +from tests.async_mock import MagicMock, patch from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed @@ -234,7 +233,7 @@ def test_dimmer_refresh_value(mock_openzwave): assert not device.is_on - with patch.object(light, "Timer", MagicMock()) as mock_timer: + with patch.object(light, "Timer") as mock_timer: value.data = 46 value_changed(value) @@ -246,7 +245,7 @@ def test_dimmer_refresh_value(mock_openzwave): assert mock_timer().start.called assert len(mock_timer().start.mock_calls) == 1 - with patch.object(light, "Timer", MagicMock()) as mock_timer_2: + with patch.object(light, "Timer") as mock_timer_2: value_changed(value) assert not device.is_on assert mock_timer().cancel.called diff --git a/tests/components/zwave/test_lock.py b/tests/components/zwave/test_lock.py index d5b6d0a0d27..2f82bcb2764 100644 --- a/tests/components/zwave/test_lock.py +++ b/tests/components/zwave/test_lock.py @@ -1,9 +1,8 @@ """Test Z-Wave locks.""" -from unittest.mock import MagicMock, patch - from homeassistant import config_entries from homeassistant.components.zwave import const, lock +from tests.async_mock import MagicMock, patch from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index 4d117179328..8306899ce02 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -1,12 +1,12 @@ """Test Z-Wave node entity.""" import unittest -from unittest.mock import MagicMock, patch import pytest from homeassistant.components.zwave import const, node_entity from homeassistant.const import ATTR_ENTITY_ID +from tests.async_mock import MagicMock, patch import tests.mock.zwave as mock_zwave diff --git a/tests/components/zwave/test_switch.py b/tests/components/zwave/test_switch.py index 4293a4a23fd..b61c456ccb9 100644 --- a/tests/components/zwave/test_switch.py +++ b/tests/components/zwave/test_switch.py @@ -1,8 +1,7 @@ """Test Z-Wave switches.""" -from unittest.mock import patch - from homeassistant.components.zwave import switch +from tests.async_mock import patch from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed diff --git a/tests/helpers/test_check_config.py b/tests/helpers/test_check_config.py index 7a5d606bcc8..ffc05544694 100644 --- a/tests/helpers/test_check_config.py +++ b/tests/helpers/test_check_config.py @@ -1,6 +1,5 @@ """Test check_config helper.""" import logging -from unittest.mock import patch from homeassistant.config import YAML_CONFIG_FILE from homeassistant.helpers.check_config import ( @@ -8,6 +7,7 @@ from homeassistant.helpers.check_config import ( async_check_ha_config_file, ) +from tests.async_mock import patch from tests.common import patch_yaml_files _LOGGER = logging.getLogger(__name__) diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 03c3965fad7..c4b87b667fa 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -1,12 +1,12 @@ """Test the condition helper.""" -from unittest.mock import patch - import pytest from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import condition from homeassistant.util import dt +from tests.async_mock import patch + async def test_invalid_condition(hass): """Test if invalid condition raises.""" diff --git a/tests/helpers/test_config_entry_oauth2_flow.py b/tests/helpers/test_config_entry_oauth2_flow.py index a72f3f51ee7..9826d60025f 100644 --- a/tests/helpers/test_config_entry_oauth2_flow.py +++ b/tests/helpers/test_config_entry_oauth2_flow.py @@ -2,13 +2,13 @@ import asyncio import logging import time -from unittest.mock import patch import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.helpers import config_entry_oauth2_flow +from tests.async_mock import patch from tests.common import MockConfigEntry, mock_platform TEST_DOMAIN = "oauth2_test" diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 1d082462849..bee463092ff 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -3,7 +3,6 @@ from datetime import date, datetime, timedelta import enum import os from socket import _GLOBAL_DEFAULT_TIMEOUT -from unittest.mock import Mock, patch import uuid import pytest @@ -12,6 +11,8 @@ import voluptuous as vol import homeassistant import homeassistant.helpers.config_validation as cv +from tests.async_mock import Mock, patch + def test_boolean(): """Test boolean validation.""" diff --git a/tests/helpers/test_deprecation.py b/tests/helpers/test_deprecation.py index 38410c3bf0f..c7e903f7b16 100644 --- a/tests/helpers/test_deprecation.py +++ b/tests/helpers/test_deprecation.py @@ -1,8 +1,8 @@ """Test deprecation helpers.""" -from unittest.mock import MagicMock, patch - from homeassistant.helpers.deprecation import deprecated_substitute, get_deprecated +from tests.async_mock import MagicMock, patch + class MockBaseClass: """Mock base class for deprecated testing.""" diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index f81d20ecd66..30f991db724 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,6 +1,5 @@ """Tests for the Device Registry.""" import asyncio -from unittest.mock import patch import pytest @@ -8,6 +7,7 @@ from homeassistant.core import callback from homeassistant.helpers import device_registry import tests.async_mock +from tests.async_mock import patch from tests.common import flush_store, mock_device_registry diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index dd734bb0dcb..70b72b1752f 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -3,7 +3,6 @@ import asyncio from datetime import timedelta import threading -from unittest.mock import MagicMock, PropertyMock, patch import pytest @@ -13,6 +12,7 @@ from homeassistant.core import Context from homeassistant.helpers import entity, entity_registry from homeassistant.helpers.entity_values import EntityValues +from tests.async_mock import MagicMock, PropertyMock, patch from tests.common import get_test_home_assistant, mock_registry @@ -139,7 +139,7 @@ async def test_warn_slow_update(hass): mock_entity.entity_id = "comp_test.test_entity" mock_entity.async_update = async_update - with patch.object(hass.loop, "call_later", MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: await mock_entity.async_update_ha_state(True) assert mock_call.called assert len(mock_call.mock_calls) == 2 @@ -169,7 +169,7 @@ async def test_warn_slow_update_with_exception(hass): mock_entity.entity_id = "comp_test.test_entity" mock_entity.async_update = async_update - with patch.object(hass.loop, "call_later", MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: await mock_entity.async_update_ha_state(True) assert mock_call.called assert len(mock_call.mock_calls) == 2 @@ -198,7 +198,7 @@ async def test_warn_slow_device_update_disabled(hass): mock_entity.entity_id = "comp_test.test_entity" mock_entity.async_update = async_update - with patch.object(hass.loop, "call_later", MagicMock()) as mock_call: + with patch.object(hass.loop, "call_later") as mock_call: await mock_entity.async_device_update(warning=False) assert not mock_call.called diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index b2bfd1f48ff..9afb8623f18 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -1,6 +1,5 @@ """Tests for the Entity Registry.""" import asyncio -from unittest.mock import patch import pytest @@ -9,6 +8,7 @@ from homeassistant.core import CoreState, callback, valid_entity_id from homeassistant.helpers import entity_registry import tests.async_mock +from tests.async_mock import patch from tests.common import MockConfigEntry, flush_store, mock_registry YAML__OPEN_PATH = "homeassistant.util.yaml.loader.open" diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index f6e375acf0b..654cf8483db 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -1,7 +1,6 @@ """Test event helpers.""" # pylint: disable=protected-access from datetime import datetime, timedelta -from unittest.mock import patch from astral import Astral import pytest @@ -27,6 +26,7 @@ from homeassistant.helpers.template import Template from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util +from tests.async_mock import patch from tests.common import async_fire_time_changed DEFAULT_TIME_ZONE = dt_util.DEFAULT_TIME_ZONE diff --git a/tests/helpers/test_integration_platform.py b/tests/helpers/test_integration_platform.py index d6c844c0d91..6f0e56d34c8 100644 --- a/tests/helpers/test_integration_platform.py +++ b/tests/helpers/test_integration_platform.py @@ -1,8 +1,7 @@ """Test integration platform helpers.""" -from unittest.mock import Mock - from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED +from tests.async_mock import Mock from tests.common import mock_platform diff --git a/tests/helpers/test_network.py b/tests/helpers/test_network.py index d4c5366b879..0fef40a82f4 100644 --- a/tests/helpers/test_network.py +++ b/tests/helpers/test_network.py @@ -1,9 +1,9 @@ """Test network helper.""" -from unittest.mock import Mock, patch - from homeassistant.components import cloud from homeassistant.helpers import network +from tests.async_mock import Mock, patch + async def test_get_external_url(hass): """Test get_external_url.""" diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index d8202b88b46..b19669d13f4 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -1,7 +1,6 @@ """Test state helpers.""" import asyncio from datetime import timedelta -from unittest.mock import patch import pytest @@ -22,6 +21,7 @@ import homeassistant.core as ha from homeassistant.helpers import state from homeassistant.util import dt as dt_util +from tests.async_mock import patch from tests.common import async_mock_service diff --git a/tests/helpers/test_sun.py b/tests/helpers/test_sun.py index b8ecd1ed86a..a877c7cdb00 100644 --- a/tests/helpers/test_sun.py +++ b/tests/helpers/test_sun.py @@ -1,12 +1,13 @@ """The tests for the Sun helpers.""" # pylint: disable=protected-access from datetime import datetime, timedelta -from unittest.mock import patch from homeassistant.const import SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET import homeassistant.helpers.sun as sun import homeassistant.util.dt as dt_util +from tests.async_mock import patch + def test_next_events(hass): """Test retrieving next sun events.""" diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 6b3e0774bd8..a698c7af6e7 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2,7 +2,6 @@ from datetime import datetime import math import random -from unittest.mock import patch import pytest import pytz @@ -21,6 +20,8 @@ from homeassistant.helpers import template import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import UnitSystem +from tests.async_mock import patch + def _set_up_units(hass): """Set up the tests.""" diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py index 9089f159761..1049108f9de 100644 --- a/tests/mock/zwave.py +++ b/tests/mock/zwave.py @@ -1,8 +1,8 @@ """Mock helpers for Z-Wave component.""" -from unittest.mock import MagicMock - from pydispatch import dispatcher +from tests.async_mock import MagicMock + def value_changed(value): """Fire a value changed.""" diff --git a/tests/scripts/test_auth.py b/tests/scripts/test_auth.py index 3ab19450879..c9ada99dc29 100644 --- a/tests/scripts/test_auth.py +++ b/tests/scripts/test_auth.py @@ -1,11 +1,10 @@ """Test the auth script to manage local users.""" -from unittest.mock import Mock, patch - import pytest from homeassistant.auth.providers import homeassistant as hass_auth from homeassistant.scripts import auth as script_auth +from tests.async_mock import Mock, patch from tests.common import register_auth_provider diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index 737c3b56ecf..d28b5f69530 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -1,10 +1,10 @@ """Test check_config script.""" import logging -from unittest.mock import patch from homeassistant.config import YAML_CONFIG_FILE import homeassistant.scripts.check_config as check_config +from tests.async_mock import patch from tests.common import get_test_config_dir, patch_yaml_files _LOGGER = logging.getLogger(__name__) diff --git a/tests/scripts/test_init.py b/tests/scripts/test_init.py index 8feef2d3384..2c14bfdcf0a 100644 --- a/tests/scripts/test_init.py +++ b/tests/scripts/test_init.py @@ -1,8 +1,8 @@ """Test script init.""" -from unittest.mock import patch - import homeassistant.scripts as scripts +from tests.async_mock import patch + @patch("homeassistant.scripts.get_default_config_dir", return_value="/default") def test_config_per_platform(mock_def): diff --git a/tests/test_main.py b/tests/test_main.py index 5ec6460301f..40c34b77b50 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,9 +1,9 @@ """Test methods in __main__.""" -from unittest.mock import PropertyMock, patch - from homeassistant import __main__ as main from homeassistant.const import REQUIRED_PYTHON_VER +from tests.async_mock import PropertyMock, patch + @patch("sys.exit") def test_validate_python(mock_exit): diff --git a/tests/util/test_async.py b/tests/util/test_async.py index 33280895ba2..b60b4097c52 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -1,12 +1,13 @@ """Tests for async util methods from Python source.""" import asyncio from unittest import TestCase -from unittest.mock import MagicMock, Mock, patch import pytest from homeassistant.util import async_ as hasync +from tests.async_mock import MagicMock, Mock, patch + @patch("asyncio.coroutines.iscoroutine") @patch("concurrent.futures.Future") diff --git a/tests/util/test_init.py b/tests/util/test_init.py index 2ffca07082b..ef5ecd898d7 100644 --- a/tests/util/test_init.py +++ b/tests/util/test_init.py @@ -1,12 +1,13 @@ """Test Home Assistant util methods.""" from datetime import datetime, timedelta -from unittest.mock import MagicMock, patch import pytest from homeassistant import util import homeassistant.util.dt as dt_util +from tests.async_mock import MagicMock, patch + def test_sanitize_filename(): """Test sanitize_filename.""" diff --git a/tests/util/test_json.py b/tests/util/test_json.py index 258f266ff78..c2b6a428515 100644 --- a/tests/util/test_json.py +++ b/tests/util/test_json.py @@ -7,7 +7,6 @@ import os import sys from tempfile import mkdtemp import unittest -from unittest.mock import Mock import pytest @@ -20,6 +19,8 @@ from homeassistant.util.json import ( save_json, ) +from tests.async_mock import Mock + # Test data that can be saved as JSON TEST_JSON_A = {"a": 1, "B": "two"} TEST_JSON_B = {"a": "one", "B": 2} diff --git a/tests/util/test_yaml.py b/tests/util/test_yaml.py index 140859ccb73..4d6f4ce3ac9 100644 --- a/tests/util/test_yaml.py +++ b/tests/util/test_yaml.py @@ -3,7 +3,6 @@ import io import logging import os import unittest -from unittest.mock import patch import pytest @@ -12,6 +11,7 @@ from homeassistant.exceptions import HomeAssistantError import homeassistant.util.yaml as yaml from homeassistant.util.yaml import loader as yaml_loader +from tests.async_mock import patch from tests.common import get_test_config_dir, patch_yaml_files From 6f6c670b3b0efdd2e98a3a3ce39b234b1dd4b1d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 3 May 2020 15:29:12 -0500 Subject: [PATCH 239/511] Make alexa and google aware of DEVICE_CLASS_GATE (#35103) --- homeassistant/components/alexa/entities.py | 4 ++-- .../components/google_assistant/const.py | 1 + tests/components/alexa/test_smart_home.py | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index e22c5c62db9..09ce71bb3bc 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -386,7 +386,7 @@ class CoverCapabilities(AlexaEntity): def default_display_categories(self): """Return the display categories for this entity.""" device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS) - if device_class == cover.DEVICE_CLASS_GARAGE: + if device_class in (cover.DEVICE_CLASS_GARAGE, cover.DEVICE_CLASS_GATE): return [DisplayCategory.GARAGE_DOOR] if device_class == cover.DEVICE_CLASS_DOOR: return [DisplayCategory.DOOR] @@ -408,7 +408,7 @@ class CoverCapabilities(AlexaEntity): def interfaces(self): """Yield the supported interfaces.""" device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS) - if device_class != cover.DEVICE_CLASS_GARAGE: + if device_class not in (cover.DEVICE_CLASS_GARAGE, cover.DEVICE_CLASS_GATE): yield AlexaPowerController(self.entity) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 37fef6f2d79..9a133b5e6b7 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -124,6 +124,7 @@ DOMAIN_TO_GOOGLE_TYPES = { DEVICE_CLASS_TO_GOOGLE_TYPES = { (cover.DOMAIN, cover.DEVICE_CLASS_GARAGE): TYPE_GARAGE, + (cover.DOMAIN, cover.DEVICE_CLASS_GATE): TYPE_GARAGE, (cover.DOMAIN, cover.DEVICE_CLASS_DOOR): TYPE_DOOR, (switch.DOMAIN, switch.DEVICE_CLASS_SWITCH): TYPE_SWITCH, (switch.DOMAIN, switch.DEVICE_CLASS_OUTLET): TYPE_OUTLET, diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 91864027873..0da4c4da6fd 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -4,6 +4,7 @@ import pytest from homeassistant.components.alexa import messages, smart_home import homeassistant.components.camera as camera +from homeassistant.components.cover import DEVICE_CLASS_GATE from homeassistant.components.media_player.const import ( SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -2630,6 +2631,28 @@ async def test_cover_garage_door(hass): ) +async def test_cover_gate(hass): + """Test gate cover discovery.""" + device = ( + "cover.test_gate", + "off", + { + "friendly_name": "Test cover gate", + "supported_features": 3, + "device_class": DEVICE_CLASS_GATE, + }, + ) + appliance = await discovery_test(device, hass) + + assert appliance["endpointId"] == "cover#test_gate" + assert appliance["displayCategories"][0] == "GARAGE_DOOR" + assert appliance["friendlyName"] == "Test cover gate" + + assert_endpoint_capabilities( + appliance, "Alexa.ModeController", "Alexa.EndpointHealth", "Alexa" + ) + + async def test_cover_position_mode(hass): """Test cover discovery and position using modeController.""" device = ( From b90cb09fd12f843d463f937c9952701c913e2874 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 3 May 2020 13:56:58 -0700 Subject: [PATCH 240/511] Add type to device registry (#35095) --- homeassistant/components/adguard/__init__.py | 1 + .../components/config/device_registry.py | 1 + homeassistant/helpers/device_registry.py | 38 ++++++++++++------- homeassistant/helpers/entity_platform.py | 1 + .../components/config/test_device_registry.py | 3 ++ tests/helpers/test_device_registry.py | 9 +++++ tests/helpers/test_entity_platform.py | 2 + 7 files changed, 42 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/adguard/__init__.py b/homeassistant/components/adguard/__init__.py index 6996a2b0d51..3e0ea2cfb74 100644 --- a/homeassistant/components/adguard/__init__.py +++ b/homeassistant/components/adguard/__init__.py @@ -206,4 +206,5 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity): "name": "AdGuard Home", "manufacturer": "AdGuard Team", "sw_version": self.hass.data[DOMAIN].get(DATA_ADGUARD_VERION), + "type": "service", } diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 08f53f948fe..5b12ccb92eb 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -71,6 +71,7 @@ def _entry_dict(entry): "model": entry.model, "name": entry.name, "sw_version": entry.sw_version, + "entry_type": entry.entry_type, "id": entry.id, "via_device_id": entry.via_device_id, "area_id": entry.area_id, diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 56b8170b99a..139c9fe2b84 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -1,7 +1,7 @@ """Provide a way to connect entities belonging to one device.""" from collections import OrderedDict import logging -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Set, Tuple import uuid import attr @@ -32,19 +32,24 @@ CONNECTION_ZIGBEE = "zigbee" class DeviceEntry: """Device Registry Entry.""" - config_entries = attr.ib(type=set, converter=set, default=attr.Factory(set)) - connections = attr.ib(type=set, converter=set, default=attr.Factory(set)) - identifiers = attr.ib(type=set, converter=set, default=attr.Factory(set)) - manufacturer = attr.ib(type=str, default=None) - model = attr.ib(type=str, default=None) - name = attr.ib(type=str, default=None) - sw_version = attr.ib(type=str, default=None) - via_device_id = attr.ib(type=str, default=None) - area_id = attr.ib(type=str, default=None) - name_by_user = attr.ib(type=str, default=None) - id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex)) + config_entries: Set[str] = attr.ib(converter=set, default=attr.Factory(set)) + connections: Set[Tuple[str, str]] = attr.ib( + converter=set, default=attr.Factory(set) + ) + identifiers: Set[Tuple[str, str]] = attr.ib( + converter=set, default=attr.Factory(set) + ) + manufacturer: str = attr.ib(default=None) + model: str = attr.ib(default=None) + name: str = attr.ib(default=None) + sw_version: str = attr.ib(default=None) + via_device_id: str = attr.ib(default=None) + area_id: str = attr.ib(default=None) + name_by_user: str = attr.ib(default=None) + entry_type: str = attr.ib(default=None) + id: str = attr.ib(default=attr.Factory(lambda: uuid.uuid4().hex)) # This value is not stored, just used to keep track of events to fire. - is_new = attr.ib(type=bool, default=False) + is_new: bool = attr.ib(default=False) def format_mac(mac: str) -> str: @@ -105,6 +110,7 @@ class DeviceRegistry: model=_UNDEF, name=_UNDEF, sw_version=_UNDEF, + entry_type=_UNDEF, via_device=None, ): """Get device. Create if it doesn't exist.""" @@ -144,6 +150,7 @@ class DeviceRegistry: model=model, name=name, sw_version=sw_version, + entry_type=entry_type, ) @callback @@ -189,6 +196,7 @@ class DeviceRegistry: model=_UNDEF, name=_UNDEF, sw_version=_UNDEF, + entry_type=_UNDEF, via_device_id=_UNDEF, area_id=_UNDEF, name_by_user=_UNDEF, @@ -236,6 +244,7 @@ class DeviceRegistry: ("model", model), ("name", name), ("sw_version", sw_version), + ("entry_type", entry_type), ("via_device_id", via_device_id), ): if value is not _UNDEF and value != getattr(old, attr_name): @@ -291,6 +300,8 @@ class DeviceRegistry: model=device["model"], name=device["name"], sw_version=device["sw_version"], + # Introduced in 0.110 + entry_type=device.get("entry_type"), id=device["id"], # Introduced in 0.79 # renamed in 0.95 @@ -323,6 +334,7 @@ class DeviceRegistry: "model": entry.model, "name": entry.name, "sw_version": entry.sw_version, + "entry_type": entry.entry_type, "id": entry.id, "via_device_id": entry.via_device_id, "area_id": entry.area_id, diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index e4d52aaa3a1..30b07c98252 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -353,6 +353,7 @@ class EntityPlatform: "model", "name", "sw_version", + "entry_type", "via_device", ): if key in device_info: diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py index a3710d48b94..c2557c83a4a 100644 --- a/tests/components/config/test_device_registry.py +++ b/tests/components/config/test_device_registry.py @@ -34,6 +34,7 @@ async def test_list_devices(hass, client, registry): manufacturer="manufacturer", model="model", via_device=("bridgeid", "0123"), + entry_type="service", ) await client.send_json({"id": 5, "type": "config/device_registry/list"}) @@ -49,6 +50,7 @@ async def test_list_devices(hass, client, registry): "model": "model", "name": None, "sw_version": None, + "entry_type": None, "via_device_id": None, "area_id": None, "name_by_user": None, @@ -60,6 +62,7 @@ async def test_list_devices(hass, client, registry): "model": "model", "name": None, "sw_version": None, + "entry_type": "service", "via_device_id": dev1, "area_id": None, "name_by_user": None, diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 30f991db724..2823ea6ca07 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -149,6 +149,7 @@ async def test_loading_from_storage(hass, hass_storage): "model": "model", "name": "name", "sw_version": "version", + "entry_type": "service", "area_id": "12345A", "name_by_user": "Test Friendly Name", } @@ -168,6 +169,7 @@ async def test_loading_from_storage(hass, hass_storage): assert entry.id == "abcdefghijklm" assert entry.area_id == "12345A" assert entry.name_by_user == "Test Friendly Name" + assert entry.entry_type == "service" assert isinstance(entry.config_entries, set) @@ -304,6 +306,9 @@ async def test_loading_saving_data(hass, registry): identifiers={("hue", "0123")}, manufacturer="manufacturer", model="via", + name="Original Name", + sw_version="Orig SW 1", + entry_type="device", ) orig_light = registry.async_get_or_create( @@ -317,6 +322,10 @@ async def test_loading_saving_data(hass, registry): assert len(registry.devices) == 2 + orig_via = registry.async_update_device( + orig_via.id, area_id="mock-area-id", name_by_user="mock-name-by-user" + ) + # Now load written data in new registry registry2 = device_registry.DeviceRegistry(hass) await flush_store(registry._store) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 3f03dfece11..eb24ea971a7 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -704,6 +704,7 @@ async def test_device_info_called(hass): "model": "test-model", "name": "test-name", "sw_version": "test-sw", + "entry_type": "service", "via_device": ("hue", "via-id"), }, ), @@ -730,6 +731,7 @@ async def test_device_info_called(hass): assert device.model == "test-model" assert device.name == "test-name" assert device.sw_version == "test-sw" + assert device.entry_type == "service" assert device.via_device_id == via.id From 39431c0764c1686c5663f3e71f5b44e2c275a272 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 3 May 2020 23:21:12 +0200 Subject: [PATCH 241/511] Add required features to cover service registration (#35073) * Fix typing in component service method * Add required features to cover service registration * Fix template cover tilt features * Delint template cover tests --- homeassistant/components/cover/__init__.py | 37 ++++++++++++++-------- homeassistant/components/template/cover.py | 2 +- homeassistant/helpers/entity_component.py | 2 +- tests/components/template/test_cover.py | 4 +-- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 84494e60c6a..ef17abc8a40 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -94,10 +94,12 @@ async def async_setup(hass, config): await component.async_setup(config) - component.async_register_entity_service(SERVICE_OPEN_COVER, {}, "async_open_cover") + component.async_register_entity_service( + SERVICE_OPEN_COVER, {}, "async_open_cover", [SUPPORT_OPEN] + ) component.async_register_entity_service( - SERVICE_CLOSE_COVER, {}, "async_close_cover" + SERVICE_CLOSE_COVER, {}, "async_close_cover", [SUPPORT_CLOSE] ) component.async_register_entity_service( @@ -108,22 +110,27 @@ async def async_setup(hass, config): ) }, "async_set_cover_position", - ) - - component.async_register_entity_service(SERVICE_STOP_COVER, {}, "async_stop_cover") - - component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle") - - component.async_register_entity_service( - SERVICE_OPEN_COVER_TILT, {}, "async_open_cover_tilt" + [SUPPORT_SET_POSITION], ) component.async_register_entity_service( - SERVICE_CLOSE_COVER_TILT, {}, "async_close_cover_tilt" + SERVICE_STOP_COVER, {}, "async_stop_cover", [SUPPORT_STOP] ) component.async_register_entity_service( - SERVICE_STOP_COVER_TILT, {}, "async_stop_cover_tilt" + SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_OPEN | SUPPORT_CLOSE] + ) + + component.async_register_entity_service( + SERVICE_OPEN_COVER_TILT, {}, "async_open_cover_tilt", [SUPPORT_OPEN_TILT] + ) + + component.async_register_entity_service( + SERVICE_CLOSE_COVER_TILT, {}, "async_close_cover_tilt", [SUPPORT_CLOSE_TILT] + ) + + component.async_register_entity_service( + SERVICE_STOP_COVER_TILT, {}, "async_stop_cover_tilt", [SUPPORT_STOP_TILT] ) component.async_register_entity_service( @@ -134,10 +141,14 @@ async def async_setup(hass, config): ) }, "async_set_cover_tilt_position", + [SUPPORT_SET_TILT_POSITION], ) component.async_register_entity_service( - SERVICE_TOGGLE_COVER_TILT, {}, "async_toggle_tilt" + SERVICE_TOGGLE_COVER_TILT, + {}, + "async_toggle_tilt", + [SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT], ) return True diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index cea91ea2963..e8bdebe2f58 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -297,7 +297,7 @@ class CoverTemplate(CoverEntity): if self._position_script is not None: supported_features |= SUPPORT_SET_POSITION - if self.current_cover_tilt_position is not None: + if self._tilt_script is not None: supported_features |= TILT_FEATURES return supported_features diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 0a7c52f7059..fb7762fb9ae 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -201,7 +201,7 @@ class EntityComponent: name: str, schema: Union[Dict[str, Any], vol.Schema], func: str, - required_features: Optional[int] = None, + required_features: Optional[List[int]] = None, ) -> None: """Register an entity service.""" if isinstance(schema, dict): diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index c8caf28ddf6..095393c8acb 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -30,8 +30,8 @@ _LOGGER = logging.getLogger(__name__) ENTITY_COVER = "cover.test_template_cover" -@pytest.fixture -def calls(hass): +@pytest.fixture(name="calls") +def calls_fixture(hass): """Track calls to a mock service.""" return async_mock_service(hass, "test", "automation") From 540cb6ea5b664b21b94b36a526b419b74a9eedf9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 3 May 2020 23:49:36 +0200 Subject: [PATCH 242/511] Upgrade spotipy to 2.12.0 (#35149) --- 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 bbbfb75a536..88e0c938a28 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.11.1"], + "requirements": ["spotipy==2.12.0"], "zeroconf": ["_spotify-connect._tcp.local."], "dependencies": ["http"], "codeowners": ["@frenck"], diff --git a/requirements_all.txt b/requirements_all.txt index 0a50553bdd2..5ef2dd1854c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1968,7 +1968,7 @@ spiderpy==1.3.1 spotcrime==1.0.4 # homeassistant.components.spotify -spotipy==2.11.1 +spotipy==2.12.0 # homeassistant.components.recorder # homeassistant.components.sql diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 02dd55bb2d2..afcbb783fba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -758,7 +758,7 @@ somecomfort==0.5.2 speak2mary==1.4.0 # homeassistant.components.spotify -spotipy==2.11.1 +spotipy==2.12.0 # homeassistant.components.recorder # homeassistant.components.sql From e7b9cecec9e3a2d2e2562dd7a454fef41f8dfa84 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 3 May 2020 23:50:13 +0200 Subject: [PATCH 243/511] Upgrade numpy to 1.18.4 (#35150) --- homeassistant/components/iqvia/manifest.json | 2 +- homeassistant/components/opencv/manifest.json | 2 +- homeassistant/components/tensorflow/manifest.json | 2 +- homeassistant/components/trend/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/iqvia/manifest.json b/homeassistant/components/iqvia/manifest.json index 154cdd43c65..0acbecddf8d 100644 --- a/homeassistant/components/iqvia/manifest.json +++ b/homeassistant/components/iqvia/manifest.json @@ -3,6 +3,6 @@ "name": "IQVIA", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/iqvia", - "requirements": ["numpy==1.18.2", "pyiqvia==0.2.1"], + "requirements": ["numpy==1.18.4", "pyiqvia==0.2.1"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/opencv/manifest.json b/homeassistant/components/opencv/manifest.json index ed54c7d05ee..37398c61686 100644 --- a/homeassistant/components/opencv/manifest.json +++ b/homeassistant/components/opencv/manifest.json @@ -2,6 +2,6 @@ "domain": "opencv", "name": "OpenCV", "documentation": "https://www.home-assistant.io/integrations/opencv", - "requirements": ["numpy==1.18.2", "opencv-python-headless==4.2.0.32"], + "requirements": ["numpy==1.18.4", "opencv-python-headless==4.2.0.32"], "codeowners": [] } diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 3e1fe5b1f5c..cbbd2d6345b 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/tensorflow", "requirements": [ "tensorflow==1.13.2", - "numpy==1.18.2", + "numpy==1.18.4", "protobuf==3.6.1", "pillow==7.1.2" ], diff --git a/homeassistant/components/trend/manifest.json b/homeassistant/components/trend/manifest.json index edd0bea977d..0d741bcf264 100644 --- a/homeassistant/components/trend/manifest.json +++ b/homeassistant/components/trend/manifest.json @@ -2,7 +2,7 @@ "domain": "trend", "name": "Trend", "documentation": "https://www.home-assistant.io/integrations/trend", - "requirements": ["numpy==1.18.2"], + "requirements": ["numpy==1.18.4"], "codeowners": [], "quality_scale": "internal" } diff --git a/requirements_all.txt b/requirements_all.txt index 5ef2dd1854c..6b1e7aab065 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -961,7 +961,7 @@ numato-gpio==0.7.1 # homeassistant.components.opencv # homeassistant.components.tensorflow # homeassistant.components.trend -numpy==1.18.2 +numpy==1.18.4 # homeassistant.components.oasa_telematics oasatelematics==0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index afcbb783fba..cd0b878e2d8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -387,7 +387,7 @@ numato-gpio==0.7.1 # homeassistant.components.opencv # homeassistant.components.tensorflow # homeassistant.components.trend -numpy==1.18.2 +numpy==1.18.4 # homeassistant.components.google oauth2client==4.0.0 From 42750088b93c2e5eb09e4078c5b1a1a6283fbd90 Mon Sep 17 00:00:00 2001 From: Ron Heft Date: Sun, 3 May 2020 19:07:14 -0400 Subject: [PATCH 244/511] Add support for refreshing synology_dsm sensors (#35141) * Add support for refreshing synology_dsm sensors Now supports `home_assistant.update_entity` service * Don't immediately update sensors on add This fixes all sensors being forcefully refreshed when setup. --- homeassistant/components/synology_dsm/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index b6a88fe5a5a..e2631b9c4ed 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -61,7 +61,7 @@ async def async_setup_entry( for sensor_type in STORAGE_DISK_SENSORS ] - async_add_entities(sensors, True) + async_add_entities(sensors) class SynoNasSensor(Entity): @@ -132,6 +132,10 @@ class SynoNasSensor(Entity): """No polling needed.""" return False + async def async_update(self): + """Only used by the generic entity update service.""" + await self._api.update() + async def async_added_to_hass(self): """Register state update callback.""" self._unsub_dispatcher = async_dispatcher_connect( From 5b7daa9e0cadc720211ee6e8a85b2b148b65de62 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 3 May 2020 16:21:10 -0700 Subject: [PATCH 245/511] Hue: Guard for when there is no brightness (#35151) --- homeassistant/components/hue/light.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 7b5b7e6e804..c9d543dba94 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -259,6 +259,9 @@ class HueLight(LightEntity): else: bri = self.light.state.get("bri") + if bri is None: + return bri + return hue_brightness_to_hass(bri) @property From 7e36699aacbc96d3814f4672327e4702e7d996ac Mon Sep 17 00:00:00 2001 From: Quentame Date: Mon, 4 May 2020 01:22:12 +0200 Subject: [PATCH 246/511] Bump pyiCloud to 0.9.7 + do not warn when pending devices (#35156) --- homeassistant/components/icloud/account.py | 2 +- homeassistant/components/icloud/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/icloud/account.py b/homeassistant/components/icloud/account.py index e0d9a608605..d039b270bb8 100644 --- a/homeassistant/components/icloud/account.py +++ b/homeassistant/components/icloud/account.py @@ -186,7 +186,7 @@ class IcloudAccount: DEVICE_STATUS_CODES.get(list(api_devices)[0][DEVICE_STATUS]) == "pending" and not self._retried_fetch ): - _LOGGER.warning("Pending devices, trying again in 15s") + _LOGGER.debug("Pending devices, trying again in 15s") self._fetch_interval = 0.25 self._retried_fetch = True else: diff --git a/homeassistant/components/icloud/manifest.json b/homeassistant/components/icloud/manifest.json index 2b8bc2fccae..40b58cbf2d0 100644 --- a/homeassistant/components/icloud/manifest.json +++ b/homeassistant/components/icloud/manifest.json @@ -3,6 +3,6 @@ "name": "Apple iCloud", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/icloud", - "requirements": ["pyicloud==0.9.6.1"], + "requirements": ["pyicloud==0.9.7"], "codeowners": ["@Quentame"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6b1e7aab065..94f133cfee9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1351,7 +1351,7 @@ pyhomeworks==0.0.6 pyialarm==0.3 # homeassistant.components.icloud -pyicloud==0.9.6.1 +pyicloud==0.9.7 # homeassistant.components.intesishome pyintesishome==1.7.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd0b878e2d8..b4e2695852c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -552,7 +552,7 @@ pyheos==0.6.0 pyhomematic==0.1.66 # homeassistant.components.icloud -pyicloud==0.9.6.1 +pyicloud==0.9.7 # homeassistant.components.ipma pyipma==2.0.5 From e4e89becc63309489555c49c885264b469ee2311 Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Mon, 4 May 2020 02:27:19 +0200 Subject: [PATCH 247/511] Return emulated hue id for update requests (#35139) * Return emulated hue id for update requests * Emulated Hue: Rename function argument to match its content * Use HTTP status consts throughout the test * Use HTTP status consts in UPnP test --- .../components/emulated_hue/hue_api.py | 10 +- tests/components/emulated_hue/test_hue_api.py | 138 +++++++++++------- tests/components/emulated_hue/test_upnp.py | 7 +- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 9637b0fb371..d7830b7b699 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -506,7 +506,9 @@ class HueOneLightChangeView(HomeAssistantView): # Create success responses for all received keys json_response = [ - create_hue_success_response(entity_id, HUE_API_STATE_ON, parsed[STATE_ON]) + create_hue_success_response( + entity_number, HUE_API_STATE_ON, parsed[STATE_ON] + ) ] for (key, val) in ( @@ -517,7 +519,7 @@ class HueOneLightChangeView(HomeAssistantView): ): if parsed[key] is not None: json_response.append( - create_hue_success_response(entity_id, val, parsed[key]) + create_hue_success_response(entity_number, val, parsed[key]) ) return self.json(json_response) @@ -710,9 +712,9 @@ def entity_to_json(config, entity): return retval -def create_hue_success_response(entity_id, attr, value): +def create_hue_success_response(entity_number, attr, value): """Create a success response for an attribute set on a light.""" - success_key = f"/lights/{entity_id}/state/{attr}" + success_key = f"/lights/{entity_number}/state/{attr}" return {"success": {success_key: value}} diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 67baed7878e..2171bac8c3f 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -34,6 +34,8 @@ from homeassistant.components.emulated_hue.hue_api import ( from homeassistant.const import ( ATTR_ENTITY_ID, HTTP_NOT_FOUND, + HTTP_OK, + HTTP_UNAUTHORIZED, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_OFF, @@ -54,6 +56,26 @@ BRIDGE_SERVER_PORT = get_test_instance_port() BRIDGE_URL_BASE = f"http://127.0.0.1:{BRIDGE_SERVER_PORT}" + "{}" JSON_HEADERS = {CONTENT_TYPE: const.CONTENT_TYPE_JSON} +ENTITY_IDS_BY_NUMBER = { + "1": "light.ceiling_lights", + "2": "light.bed_light", + "3": "script.set_kitchen_light", + "4": "light.kitchen_lights", + "5": "media_player.living_room", + "6": "media_player.bedroom", + "7": "media_player.walkman", + "8": "media_player.lounge_room", + "9": "fan.living_room_fan", + "10": "fan.ceiling_fan", + "11": "cover.living_room_window", + "12": "climate.hvac", + "13": "climate.heatpump", + "14": "climate.ecobee", + "15": "light.no_brightness", +} + +ENTITY_NUMBERS_BY_ID = {v: k for k, v in ENTITY_IDS_BY_NUMBER.items()} + @pytest.fixture def hass_hue(loop, hass): @@ -144,7 +166,6 @@ def hue_client(loop, hass_hue, aiohttp_client): config = Config( None, { - emulated_hue.CONF_TYPE: emulated_hue.TYPE_ALEXA, emulated_hue.CONF_ENTITIES: { "light.bed_light": {emulated_hue.CONF_ENTITY_HIDDEN: True}, # Kitchen light is explicitly excluded from being exposed @@ -162,6 +183,7 @@ def hue_client(loop, hass_hue, aiohttp_client): }, }, ) + config.numbers = ENTITY_IDS_BY_NUMBER HueUsernameView().register(web_app, web_app.router) HueAllLightsStateView(config).register(web_app, web_app.router) @@ -177,7 +199,7 @@ async def test_discover_lights(hue_client): """Test the discovery of lights.""" result = await hue_client.get("/api/username/lights") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() @@ -204,7 +226,7 @@ async def test_discover_lights(hue_client): async def test_light_without_brightness_supported(hass_hue, hue_client): """Test that light without brightness is supported.""" light_without_brightness_json = await perform_get_light_state( - hue_client, "light.no_brightness", 200 + hue_client, "light.no_brightness", HTTP_OK ) assert light_without_brightness_json["state"][HUE_API_STATE_ON] is True @@ -223,7 +245,7 @@ async def test_light_without_brightness_can_be_turned_off(hass_hue, hue_client): ) no_brightness_result_json = await no_brightness_result.json() - assert no_brightness_result.status == 200 + assert no_brightness_result.status == HTTP_OK assert "application/json" in no_brightness_result.headers["content-type"] assert len(no_brightness_result_json) == 1 @@ -255,7 +277,7 @@ async def test_light_without_brightness_can_be_turned_on(hass_hue, hue_client): no_brightness_result_json = await no_brightness_result.json() - assert no_brightness_result.status == 200 + assert no_brightness_result.status == HTTP_OK assert "application/json" in no_brightness_result.headers["content-type"] assert len(no_brightness_result_json) == 1 @@ -283,7 +305,7 @@ async def test_reachable_for_state(hass_hue, hue_client, state, is_reachable): hass_hue.states.async_set(entity_id, state) - state_json = await perform_get_light_state(hue_client, entity_id, 200) + state_json = await perform_get_light_state(hue_client, entity_id, HTTP_OK) assert state_json["state"]["reachable"] == is_reachable, state_json @@ -292,7 +314,7 @@ async def test_discover_full_state(hue_client): """Test the discovery of full state.""" result = await hue_client.get(f"/api/{HUE_API_USERNAME}") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() @@ -344,7 +366,9 @@ async def test_get_light_state(hass_hue, hue_client): blocking=True, ) - office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) + office_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", HTTP_OK + ) assert office_json["state"][HUE_API_STATE_ON] is True assert office_json["state"][HUE_API_STATE_BRI] == 127 @@ -354,13 +378,18 @@ async def test_get_light_state(hass_hue, hue_client): # Check all lights view result = await hue_client.get("/api/username/lights") - assert result.status == 200 + assert result.status == HTTP_OK assert "application/json" in result.headers["content-type"] result_json = await result.json() - assert "light.ceiling_lights" in result_json - assert result_json["light.ceiling_lights"]["state"][HUE_API_STATE_BRI] == 127 + assert ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] in result_json + assert ( + result_json[ENTITY_NUMBERS_BY_ID["light.ceiling_lights"]]["state"][ + HUE_API_STATE_BRI + ] + == 127 + ) # Turn office light off await hass_hue.services.async_call( @@ -370,7 +399,9 @@ async def test_get_light_state(hass_hue, hue_client): blocking=True, ) - office_json = await perform_get_light_state(hue_client, "light.ceiling_lights", 200) + office_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", HTTP_OK + ) assert office_json["state"][HUE_API_STATE_ON] is False # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 @@ -378,10 +409,10 @@ async def test_get_light_state(hass_hue, hue_client): assert office_json["state"][HUE_API_STATE_SAT] == 0 # Make sure bedroom light isn't accessible - await perform_get_light_state(hue_client, "light.bed_light", 401) + await perform_get_light_state(hue_client, "light.bed_light", HTTP_UNAUTHORIZED) # Make sure kitchen light isn't accessible - await perform_get_light_state(hue_client, "light.kitchen_lights", 401) + await perform_get_light_state(hue_client, "light.kitchen_lights", HTTP_UNAUTHORIZED) async def test_put_light_state(hass, hass_hue, hue_client): @@ -432,7 +463,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): # go through api to get the state back ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) assert ceiling_json["state"][HUE_API_STATE_BRI] == 123 assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 @@ -451,7 +482,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): # go through api to get the state back ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) assert ceiling_json["state"][HUE_API_STATE_BRI] == 254 assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 @@ -464,7 +495,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): ceiling_result_json = await ceiling_result.json() - assert ceiling_result.status == 200 + assert ceiling_result.status == HTTP_OK assert "application/json" in ceiling_result.headers["content-type"] assert len(ceiling_result_json) == 1 @@ -473,7 +504,7 @@ async def test_put_light_state(hass, hass_hue, hue_client): ceiling_lights = hass_hue.states.get("light.ceiling_lights") assert ceiling_lights.state == STATE_OFF ceiling_json = await perform_get_light_state( - hue_client, "light.ceiling_lights", 200 + hue_client, "light.ceiling_lights", HTTP_OK ) # Removed assert HUE_API_STATE_BRI == 0 as Hue API states bri must be 1..254 assert ceiling_json["state"][HUE_API_STATE_HUE] == 0 @@ -483,13 +514,13 @@ async def test_put_light_state(hass, hass_hue, hue_client): bedroom_result = await perform_put_light_state( hass_hue, hue_client, "light.bed_light", True ) - assert bedroom_result.status == 401 + assert bedroom_result.status == HTTP_UNAUTHORIZED # Make sure we can't change the kitchen light state kitchen_result = await perform_put_light_state( - hass_hue, hue_client, "light.kitchen_light", True + hass_hue, hue_client, "light.kitchen_lights", True ) - assert kitchen_result.status == HTTP_NOT_FOUND + assert kitchen_result.status == HTTP_UNAUTHORIZED async def test_put_light_state_script(hass, hass_hue, hue_client): @@ -512,7 +543,7 @@ async def test_put_light_state_script(hass, hass_hue, hue_client): script_result_json = await script_result.json() - assert script_result.status == 200 + assert script_result.status == HTTP_OK assert len(script_result_json) == 2 kitchen_light = hass_hue.states.get("light.kitchen_lights") @@ -535,7 +566,7 @@ async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): hvac_result_json = await hvac_result.json() - assert hvac_result.status == 200 + assert hvac_result.status == HTTP_OK assert len(hvac_result_json) == 2 hvac = hass_hue.states.get("climate.hvac") @@ -546,7 +577,7 @@ async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): ecobee_result = await perform_put_light_state( hass_hue, hue_client, "climate.ecobee", True ) - assert ecobee_result.status == 401 + assert ecobee_result.status == HTTP_UNAUTHORIZED async def test_put_light_state_media_player(hass_hue, hue_client): @@ -569,7 +600,7 @@ async def test_put_light_state_media_player(hass_hue, hue_client): mp_result_json = await mp_result.json() - assert mp_result.status == 200 + assert mp_result.status == HTTP_OK assert len(mp_result_json) == 2 walkman = hass_hue.states.get("media_player.walkman") @@ -604,7 +635,7 @@ async def test_close_cover(hass_hue, hue_client): hass_hue, hue_client, cover_id, True, 100 ) - assert cover_result.status == 200 + assert cover_result.status == HTTP_OK assert "application/json" in cover_result.headers["content-type"] for _ in range(7): @@ -624,6 +655,7 @@ async def test_close_cover(hass_hue, hue_client): async def test_set_position_cover(hass_hue, hue_client): """Test setting position cover .""" cover_id = "cover.living_room_window" + cover_number = ENTITY_NUMBERS_BY_ID[cover_id] # Turn the office light off first await hass_hue.services.async_call( cover.DOMAIN, @@ -651,19 +683,14 @@ async def test_set_position_cover(hass_hue, hue_client): hass_hue, hue_client, cover_id, False, brightness ) - assert cover_result.status == 200 + assert cover_result.status == HTTP_OK assert "application/json" in cover_result.headers["content-type"] cover_result_json = await cover_result.json() assert len(cover_result_json) == 2 - assert True, cover_result_json[0]["success"][ - "/lights/cover.living_room_window/state/on" - ] - assert ( - cover_result_json[1]["success"]["/lights/cover.living_room_window/state/bri"] - == level - ) + assert True, cover_result_json[0]["success"][f"/lights/{cover_number}/state/on"] + assert cover_result_json[1]["success"][f"/lights/{cover_number}/state/bri"] == level for _ in range(100): future = dt_util.utcnow() + timedelta(seconds=1) @@ -696,7 +723,7 @@ async def test_put_light_state_fan(hass_hue, hue_client): fan_result_json = await fan_result.json() - assert fan_result.status == 200 + assert fan_result.status == HTTP_OK assert len(fan_result_json) == 2 living_room_fan = hass_hue.states.get("fan.living_room_fan") @@ -707,6 +734,7 @@ async def test_put_light_state_fan(hass_hue, hue_client): # pylint: disable=invalid-name async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): """Test the form with urlencoded content.""" + entity_number = ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] # Needed for Alexa await perform_put_test_on_ceiling_lights( hass_hue, hue_client, "application/x-www-form-urlencoded" @@ -715,7 +743,7 @@ async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): # Make sure we fail gracefully when we can't parse the data data = {"key1": "value1", "key2": "value2"} result = await hue_client.put( - "/api/username/lights/light.ceiling_lights/state", + f"/api/username/lights/{entity_number}/state", headers={"content-type": "application/x-www-form-urlencoded"}, data=data, ) @@ -725,22 +753,26 @@ async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): async def test_entity_not_found(hue_client): """Test for entity which are not found.""" - result = await hue_client.get("/api/username/lights/not.existant_entity") + result = await hue_client.get("/api/username/lights/98") assert result.status == HTTP_NOT_FOUND - result = await hue_client.put("/api/username/lights/not.existant_entity/state") + result = await hue_client.put("/api/username/lights/98/state") assert result.status == HTTP_NOT_FOUND async def test_allowed_methods(hue_client): """Test the allowed methods.""" - result = await hue_client.get("/api/username/lights/light.ceiling_lights/state") + result = await hue_client.get( + "/api/username/lights/ENTITY_NUMBERS_BY_ID[light.ceiling_lights]/state" + ) assert result.status == 405 - result = await hue_client.put("/api/username/lights/light.ceiling_lights") + result = await hue_client.put( + "/api/username/lights/ENTITY_NUMBERS_BY_ID[light.ceiling_lights]" + ) assert result.status == 405 @@ -753,7 +785,9 @@ async def test_proper_put_state_request(hue_client): """Test the request to set the state.""" # Test proper on value parsing result = await hue_client.put( - "/api/username/lights/{}/state".format("light.ceiling_lights"), + "/api/username/lights/{}/state".format( + ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] + ), data=json.dumps({HUE_API_STATE_ON: 1234}), ) @@ -761,7 +795,9 @@ async def test_proper_put_state_request(hue_client): # Test proper brightness value parsing result = await hue_client.put( - "/api/username/lights/{}/state".format("light.ceiling_lights"), + "/api/username/lights/{}/state".format( + ENTITY_NUMBERS_BY_ID["light.ceiling_lights"] + ), data=json.dumps({HUE_API_STATE_ON: True, HUE_API_STATE_BRI: "Hello world!"}), ) @@ -773,7 +809,7 @@ async def test_get_empty_groups_state(hue_client): # Test proper on value parsing result = await hue_client.get("/api/username/groups") - assert result.status == 200 + assert result.status == HTTP_OK result_json = await result.json() @@ -801,7 +837,7 @@ async def perform_put_test_on_ceiling_lights( hass_hue, hue_client, "light.ceiling_lights", True, 56, content_type ) - assert office_result.status == 200 + assert office_result.status == HTTP_OK assert "application/json" in office_result.headers["content-type"] office_result_json = await office_result.json() @@ -816,11 +852,12 @@ async def perform_put_test_on_ceiling_lights( async def perform_get_light_state(client, entity_id, expected_status): """Test the getting of a light state.""" - result = await client.get(f"/api/username/lights/{entity_id}") + entity_number = ENTITY_NUMBERS_BY_ID[entity_id] + result = await client.get(f"/api/username/lights/{entity_number}") assert result.status == expected_status - if expected_status == 200: + if expected_status == HTTP_OK: assert "application/json" in result.headers["content-type"] return await result.json() @@ -850,8 +887,9 @@ async def perform_put_light_state( if saturation is not None: data[HUE_API_STATE_SAT] = saturation + entity_number = ENTITY_NUMBERS_BY_ID[entity_id] result = await client.put( - f"/api/username/lights/{entity_id}/state", + f"/api/username/lights/{entity_number}/state", headers=req_headers, data=json.dumps(data).encode(), ) @@ -878,12 +916,12 @@ async def test_external_ip_blocked(hue_client): ): for getUrl in getUrls: result = await hue_client.get(getUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED for postUrl in postUrls: result = await hue_client.post(postUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED for putUrl in putUrls: result = await hue_client.put(putUrl) - assert result.status == 401 + assert result.status == HTTP_UNAUTHORIZED diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 2557ecac2dd..110ecf868e6 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -8,6 +8,7 @@ import requests from homeassistant import const, setup from homeassistant.components import emulated_hue +from homeassistant.const import HTTP_OK from tests.async_mock import patch from tests.common import get_test_home_assistant, get_test_instance_port @@ -51,7 +52,7 @@ class TestEmulatedHue(unittest.TestCase): """Test the description.""" result = requests.get(BRIDGE_URL_BASE.format("/description.xml"), timeout=5) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "text/xml" in result.headers["content-type"] # Make sure the XML is parsable @@ -68,7 +69,7 @@ class TestEmulatedHue(unittest.TestCase): BRIDGE_URL_BASE.format("/api"), data=json.dumps(request_json), timeout=5 ) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "application/json" in result.headers["content-type"] resp_json = result.json() @@ -87,7 +88,7 @@ class TestEmulatedHue(unittest.TestCase): timeout=5, ) - assert result.status_code == 200 + assert result.status_code == HTTP_OK assert "application/json" in result.headers["content-type"] resp_json = result.json() From 73fb57fd32b1589459d8c99dc2c1c43bc180d4e7 Mon Sep 17 00:00:00 2001 From: Andreas Oberritter Date: Mon, 4 May 2020 06:20:00 +0200 Subject: [PATCH 248/511] Support multiple EDL21 meters (#33594) * edl21: Add 'name' option * edl21: Include domain and electricity ID in unique ID As a side-effect, this makes electricity IDs mandatory, i.e. devices which don't transmit their ID won't register any sensors. * edl21: Implement upgrade path for unique IDs * Update homeassistant/components/edl21/sensor.py Drop DOMAIN from unique ID. Co-Authored-By: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/edl21/sensor.py | 72 +++++++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index a4e0ca734fa..6a9330cae1c 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -8,6 +8,7 @@ from sml.asyncio import SmlProtocol import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( @@ -15,6 +16,7 @@ from homeassistant.helpers.dispatcher import ( 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 @@ -26,7 +28,12 @@ ICON_POWER = "mdi:flash" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) SIGNAL_EDL21_TELEGRAM = "edl21_telegram" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_SERIAL_PORT): cv.string}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SERIAL_PORT): cv.string, + vol.Optional(CONF_NAME, default=""): cv.string, + }, +) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -74,6 +81,7 @@ class EDL21: self._registered_obis = set() self._hass = hass self._async_add_entities = async_add_entities + self._name = config[CONF_NAME] self._proto = SmlProtocol(config[CONF_SERIAL_PORT]) self._proto.add_listener(self.event, ["SmlGetListResponse"]) @@ -85,19 +93,35 @@ class EDL21: """Handle events from pysml.""" assert isinstance(message_body, SmlGetListResponse) + electricity_id = None + for telegram in message_body.get("valList", []): + if telegram.get("objName") == "1-0:0.0.9*255": + electricity_id = telegram.get("value") + break + + if electricity_id is None: + return + electricity_id = electricity_id.replace(" ", "") + new_entities = [] for telegram in message_body.get("valList", []): obis = telegram.get("objName") if not obis: continue - if obis in self._registered_obis: - async_dispatcher_send(self._hass, SIGNAL_EDL21_TELEGRAM, telegram) + if (electricity_id, obis) in self._registered_obis: + async_dispatcher_send( + self._hass, SIGNAL_EDL21_TELEGRAM, electricity_id, telegram + ) else: name = self._OBIS_NAMES.get(obis) if name: - new_entities.append(EDL21Entity(obis, name, telegram)) - self._registered_obis.add(obis) + if self._name: + name = f"{self._name}: {name}" + new_entities.append( + EDL21Entity(electricity_id, obis, name, telegram) + ) + self._registered_obis.add((electricity_id, obis)) elif obis not in self._OBIS_BLACKLIST: _LOGGER.warning( "Unhandled sensor %s detected. Please report at " @@ -107,16 +131,41 @@ class EDL21: self._OBIS_BLACKLIST.add(obis) if new_entities: - self._async_add_entities(new_entities, update_before_add=True) + self._hass.loop.create_task(self.add_entities(new_entities)) + + async def add_entities(self, new_entities) -> None: + """Migrate old unique IDs, then add entities to hass.""" + registry = await async_get_registry(self._hass) + + for entity in new_entities: + old_entity_id = registry.async_get_entity_id( + "sensor", DOMAIN, entity.old_unique_id + ) + if old_entity_id is not None: + _LOGGER.debug( + "Migrating unique_id from [%s] to [%s]", + entity.old_unique_id, + entity.unique_id, + ) + if registry.async_get_entity_id("sensor", DOMAIN, entity.unique_id): + registry.async_remove(old_entity_id) + else: + registry.async_update_entity( + old_entity_id, new_unique_id=entity.unique_id + ) + + self._async_add_entities(new_entities, update_before_add=True) class EDL21Entity(Entity): """Entity reading values from EDL21 telegram.""" - def __init__(self, obis, name, telegram): + def __init__(self, electricity_id, obis, name, telegram): """Initialize an EDL21Entity.""" + self._electricity_id = electricity_id self._obis = obis self._name = name + self._unique_id = f"{electricity_id}_{obis}" self._telegram = telegram self._min_time = MIN_TIME_BETWEEN_UPDATES self._last_update = utcnow() @@ -132,8 +181,10 @@ class EDL21Entity(Entity): """Run when entity about to be added to hass.""" @callback - def handle_telegram(telegram): + def handle_telegram(electricity_id, telegram): """Update attributes from last received telegram for this object.""" + if self._electricity_id != electricity_id: + return if self._obis != telegram.get("objName"): return if self._telegram == telegram: @@ -164,6 +215,11 @@ class EDL21Entity(Entity): @property def unique_id(self) -> str: """Return a unique ID.""" + return self._unique_id + + @property + def old_unique_id(self) -> str: + """Return a less unique ID as used in the first version of edl21.""" return self._obis @property From c5379a0f3533538a494874cd4a341397789fdeb1 Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Mon, 4 May 2020 07:18:33 +0200 Subject: [PATCH 249/511] Improve emulated_hue compatibility with newer systems (#35148) * Make emulated hue detectable by Busch-Jaeger free@home SysAP * Emulated hue: Remove unnecessary host line from UPnP response * Test that external IPs are blocked for config route * Add another test for unauthorized users * Change hue username to nouser nouser seems to be used by the official Hue Bridge v1 Android app and is used by other projects as well Co-authored-by: J. Nick Koston --- .../components/emulated_hue/__init__.py | 2 + .../components/emulated_hue/hue_api.py | 35 +++++++++- homeassistant/components/emulated_hue/upnp.py | 10 +-- tests/components/emulated_hue/test_hue_api.py | 66 ++++++++++++++++++- tests/components/emulated_hue/test_upnp.py | 4 +- 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index da6e7acab40..5dbd52d09b1 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -14,6 +14,7 @@ from homeassistant.util.json import load_json, save_json from .hue_api import ( HueAllGroupsStateView, HueAllLightsStateView, + HueConfigView, HueFullStateView, HueGroupView, HueOneLightChangeView, @@ -119,6 +120,7 @@ async def async_setup(hass, yaml_config): HueAllGroupsStateView(config).register(app, app.router) HueGroupView(config).register(app, app.router) HueFullStateView(config).register(app, app.router) + HueConfigView(config).register(app, app.router) upnp_listener = UPNPResponderThread( config.host_ip_addr, diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index d7830b7b699..069a4b60d0c 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -89,7 +89,7 @@ HUE_API_STATE_SAT_MAX = 254 HUE_API_STATE_CT_MIN = 153 # Color temp HUE_API_STATE_CT_MAX = 500 -HUE_API_USERNAME = "12345678901234567890" +HUE_API_USERNAME = "nouser" UNAUTHORIZED_USER = [ {"error": {"address": "/", "description": "unauthorized user", "type": "1"}} ] @@ -226,14 +226,47 @@ class HueFullStateView(HomeAssistantView): "config": { "mac": "00:00:00:00:00:00", "swversion": "01003542", + "apiversion": "1.17.0", "whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}}, "ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}", + "linkbutton": True, }, } return self.json(json_response) +class HueConfigView(HomeAssistantView): + """Return config view of emulated hue.""" + + url = "/api/{username}/config" + name = "emulated_hue:username:config" + requires_auth = False + + def __init__(self, config): + """Initialize the instance of the view.""" + self.config = config + + @core.callback + def get(self, request, username): + """Process a request to get the configuration.""" + if not is_local(request[KEY_REAL_IP]): + return self.json_message("only local IPs allowed", HTTP_UNAUTHORIZED) + if username != HUE_API_USERNAME: + return self.json(UNAUTHORIZED_USER) + + json_response = { + "mac": "00:00:00:00:00:00", + "swversion": "01003542", + "apiversion": "1.17.0", + "whitelist": {HUE_API_USERNAME: {"name": "HASS BRIDGE"}}, + "ipaddress": f"{self.config.advertise_ip}:{self.config.advertise_port}", + "linkbutton": True, + } + + return self.json(json_response) + + class HueOneLightStateView(HomeAssistantView): """Handle requests for getting info about a single entity.""" diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index c10fb3b826b..f0fe392f865 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -42,7 +42,7 @@ class DescriptionXmlView(HomeAssistantView): Philips hue bridge 2015 BSB002 http://www.meethue.com -1234 +001788FFFE23BFC2 uuid:2f402f80-da50-11e1-9b23-001788255acc @@ -77,10 +77,10 @@ class UPNPResponderThread(threading.Thread): CACHE-CONTROL: max-age=60 EXT: LOCATION: http://{advertise_ip}:{advertise_port}/description.xml -SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/0.1 -hue-bridgeid: 1234 -ST: urn:schemas-upnp-org:device:basic:1 -USN: uuid:Socket-1_0-221438K0100073::urn:schemas-upnp-org:device:basic:1 +SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0 +hue-bridgeid: 001788FFFE23BFC2 +ST: upnp:rootdevice +USN: uuid:2f402f80-da50-11e1-9b23-00178829d301::upnp:rootdevice """ diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 2171bac8c3f..fa97cd2f417 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -26,6 +26,7 @@ from homeassistant.components.emulated_hue.hue_api import ( HUE_API_USERNAME, HueAllGroupsStateView, HueAllLightsStateView, + HueConfigView, HueFullStateView, HueOneLightChangeView, HueOneLightStateView, @@ -191,6 +192,7 @@ def hue_client(loop, hass_hue, aiohttp_client): HueOneLightChangeView(config).register(web_app, web_app.router) HueAllGroupsStateView(config).register(web_app, web_app.router) HueFullStateView(config).register(web_app, web_app.router) + HueConfigView(config).register(web_app, web_app.router) return loop.run_until_complete(aiohttp_client(web_app)) @@ -330,7 +332,7 @@ async def test_discover_full_state(hue_client): # Make sure array is correct size assert len(result_json) == 2 - assert len(config_json) == 4 + assert len(config_json) == 6 assert len(lights_json) >= 1 # Make sure the config wrapper added to the config is there @@ -341,6 +343,10 @@ async def test_discover_full_state(hue_client): assert "swversion" in config_json assert "01003542" in config_json["swversion"] + # Make sure the api version is correct + assert "apiversion" in config_json + assert "1.17.0" in config_json["apiversion"] + # Make sure the correct username in config assert "whitelist" in config_json assert HUE_API_USERNAME in config_json["whitelist"] @@ -351,6 +357,49 @@ async def test_discover_full_state(hue_client): assert "ipaddress" in config_json assert "127.0.0.1:8300" in config_json["ipaddress"] + # Make sure the device announces a link button + assert "linkbutton" in config_json + assert config_json["linkbutton"] is True + + +async def test_discover_config(hue_client): + """Test the discovery of configuration.""" + result = await hue_client.get(f"/api/{HUE_API_USERNAME}/config") + + assert result.status == 200 + assert "application/json" in result.headers["content-type"] + + config_json = await result.json() + + # Make sure array is correct size + assert len(config_json) == 6 + + # Make sure the config wrapper added to the config is there + assert "mac" in config_json + assert "00:00:00:00:00:00" in config_json["mac"] + + # Make sure the correct version in config + assert "swversion" in config_json + assert "01003542" in config_json["swversion"] + + # Make sure the api version is correct + assert "apiversion" in config_json + assert "1.17.0" in config_json["apiversion"] + + # Make sure the correct username in config + assert "whitelist" in config_json + assert HUE_API_USERNAME in config_json["whitelist"] + assert "name" in config_json["whitelist"][HUE_API_USERNAME] + assert "HASS BRIDGE" in config_json["whitelist"][HUE_API_USERNAME]["name"] + + # Make sure the correct ip in config + assert "ipaddress" in config_json + assert "127.0.0.1:8300" in config_json["ipaddress"] + + # Make sure the device announces a link button + assert "linkbutton" in config_json + assert config_json["linkbutton"] is True + async def test_get_light_state(hass_hue, hue_client): """Test the getting of light state.""" @@ -905,6 +954,7 @@ async def test_external_ip_blocked(hue_client): getUrls = [ "/api/username/groups", "/api/username", + "/api/username/config", "/api/username/lights", "/api/username/lights/light.ceiling_lights", ] @@ -925,3 +975,17 @@ async def test_external_ip_blocked(hue_client): for putUrl in putUrls: result = await hue_client.put(putUrl) assert result.status == HTTP_UNAUTHORIZED + + +async def test_unauthorized_user_blocked(hue_client): + """Test unauthorized_user blocked.""" + getUrls = [ + "/api/wronguser", + "/api/wronguser/config", + ] + for getUrl in getUrls: + result = await hue_client.get(getUrl) + assert result.status == HTTP_OK + + result_json = await result.json() + assert result_json[0]["error"]["description"] == "unauthorized user" diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 110ecf868e6..32859ca00c1 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -57,7 +57,9 @@ class TestEmulatedHue(unittest.TestCase): # Make sure the XML is parsable try: - ET.fromstring(result.text) + root = ET.fromstring(result.text) + ns = {"s": "urn:schemas-upnp-org:device-1-0"} + assert root.find("./s:device/s:serialNumber", ns).text == "001788FFFE23BFC2" except: # noqa: E722 pylint: disable=bare-except self.fail("description.xml is not valid XML!") From 7284f4e7ca74fe3f9f2c56efd6ff2927e137758a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 4 May 2020 09:28:20 +0200 Subject: [PATCH 250/511] Upgrade pyupgrade to v2.3.0 (#35159) --- .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 8d7efbf00f1..491cfc05d8a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.2.1 + rev: v2.3.0 hooks: - id: pyupgrade args: [--py37-plus] diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index e64e499baf7..798377780ff 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -7,5 +7,5 @@ flake8-docstrings==1.5.0 flake8==3.7.9 isort==4.3.21 pydocstyle==5.0.2 -pyupgrade==2.2.1 +pyupgrade==2.3.0 yamllint==1.23.0 From 96a998ad25ca739d197555a6c7c2e7ff96edefaf Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 4 May 2020 10:35:40 +0200 Subject: [PATCH 251/511] Fix Canary KeyError: 'ffmpeg_arguments' (#35158) --- homeassistant/components/canary/alarm_control_panel.py | 5 +---- homeassistant/components/canary/camera.py | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index a5930e658fb..ea0e3078b0c 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -24,10 +24,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Canary alarms.""" data = hass.data[DATA_CANARY] - devices = [] - - for location in data.locations: - devices.append(CanaryAlarm(data, location.location_id)) + devices = [CanaryAlarm(data, location.location_id) for location in data.locations] add_entities(devices, True) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 870256ffcff..3ba7f094da1 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -29,6 +29,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Canary sensors.""" + if discovery_info is not None: + return + data = hass.data[DATA_CANARY] devices = [] From 49979d0a75e4a15115e206fd56f74239d9a253bf Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Mon, 4 May 2020 10:38:26 +0200 Subject: [PATCH 252/511] Upgrade cryptography to 2.9.2 (#35084) --- homeassistant/package_constraints.txt | 2 +- requirements_all.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 b7e70914714..d35e5302a85 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -8,7 +8,7 @@ attrs==19.3.0 bcrypt==3.1.7 certifi>=2020.4.5.1 ciso8601==2.1.3 -cryptography==2.9 +cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 diff --git a/requirements_all.txt b/requirements_all.txt index 94f133cfee9..ef33afad153 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -9,7 +9,7 @@ ciso8601==2.1.3 importlib-metadata==1.6.0 jinja2>=2.11.1 PyJWT==1.7.1 -cryptography==2.9 +cryptography==2.9.2 pip>=8.0.3 python-slugify==4.0.0 pytz>=2019.03 diff --git a/setup.py b/setup.py index 0c56e89b67c..d0cfb5a32bb 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ REQUIRES = [ "jinja2>=2.11.1", "PyJWT==1.7.1", # PyJWT has loose dependency. We want the latest one. - "cryptography==2.9", + "cryptography==2.9.2", "pip>=8.0.3", "python-slugify==4.0.0", "pytz>=2019.03", From 7a73c6adf7b85bab08dea56ee32f3b2026e416e0 Mon Sep 17 00:00:00 2001 From: David Beitey Date: Mon, 4 May 2020 18:45:40 +1000 Subject: [PATCH 253/511] scrape: extract strings from new non-text tags (#35021) With the upgrade to beautifulsoup4 to 4.9.0 (#34007), certain tags (`